plv8/0000775000401600040160000000000013073210527010011 5ustar cbecbeplv8/Changes0000664000401600040160000000434713064535725011326 0ustar cbecbeRevision history for plv8 1.4.8 2016-04-20 - better management of resources 1.4.4 2015-05-26 - Add jsonb type coercion in function boundary. - Fix crash related to FLEXIBLE_ARRAY_MEMBER changes. 1.4.3 2015-02-02 - Fix compilation for 9.4 - Fix off-by-one in variadic .execute handling - Add .gitignore - Fix incorrect code in return_next - Mark NULL in converter even for dropped columns to prevent crash. 1.4.2 2014-02-20 - Fix CREATE FUNCTION in startup procedure. - Fix dropped column case. - Accept non-array parameters to execute(). - Prevent privilege escalation in explicit calls to validators. 1.4.1 2013-05-14 - Fix compilation with v8 3.18.x 1.4.0 2013-04-29 - Implement fetch(n) and move(n). - Fix a bug around type conversion. - Support recent version of v8. - Disallow use of plv8.execute() when outside of transaction. - Fix some memory leaks. - Remove use of gettext(). - Upgrade LiveScript to 1.1.1. 1.3.0 2012-12-08 - Make two dialects (plcoffee, plls) official sub-extensions. - Implement builtin json type conversion. - Static build and automatic v8 build via 'static' target. - Implement v8's remote debugger if enabled. - Implement bytea type conversion and typed array. - Allow polymorphic and internal types in argument and return types. - Support user defined window functions. - Potential bug fixes. 1.2.1 2012-12-08 - Fix a crash in returning array value from array-return function. - Fix trigger so that returned NULL can be handled correctly. 1.2.0 2012-07-20 - Check the field names match for SRFs. - Fix EpochToDate to handle non integer timestamp case. - Let parser deduce parameter types in SPI queries. 1.1.0 2012-06-07 - Add plv8.version string. - Fix a bug around name[] conversion from/to SQL. 1.1.0beta1 2012-05-13 - Initial public beta. plv8/.travis.yml0000664000401600040160000000403413064535725012135 0ustar cbecbebefore_install: - psql --version - sudo /etc/init.d/postgresql stop - sudo apt-get -y --purge remove postgresql libpq-dev libpq5 postgresql-client-common postgresql-common - sudo rm -rf /var/lib/postgresql - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - - sudo sh -c "echo deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main $PGVERSION >> /etc/apt/sources.list.d/postgresql.list" - sudo apt-get update -qq - sudo apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::="--force-confnew" install postgresql-$PGVERSION postgresql-server-dev-$PGVERSION - sudo chmod 777 /etc/postgresql/$PGVERSION/main/pg_hba.conf - sudo echo "local all postgres trust" > /etc/postgresql/$PGVERSION/main/pg_hba.conf - sudo echo "local all all trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf - sudo echo "host all all 127.0.0.1/32 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf - sudo echo "host all all ::1/128 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf - sudo /etc/init.d/postgresql restart before_script: - git clone -b $v8 --depth 10 git://github.com/v8/v8.git - cd v8 && git describe --always - make dependencies && make native library=shared -j8 - sudo install -v --mode=0644 include/* /usr/include - sudo install -v --mode=0644 out/native/lib.target/libv8.so /usr/lib/libv8.so - sudo install -v out/native/d8 /usr/bin/d8 - cd .. env: matrix: - PGVERSION=9.4 v8=3.18 - PGVERSION=9.4 v8=3.20 - PGVERSION=9.4 v8=3.22 - PGVERSION=9.3 v8=3.18 - PGVERSION=9.3 v8=3.20 - PGVERSION=9.3 v8=3.22 - PGVERSION=9.2 v8=3.18 - PGVERSION=9.2 v8=3.20 - PGVERSION=9.2 v8=3.22 - PGVERSION=9.1 v8=3.18 - PGVERSION=9.1 v8=3.20 - PGVERSION=9.1 v8=3.22 - PGVERSION=9.0 v8=3.18 - PGVERSION=9.0 v8=3.20 - PGVERSION=9.0 v8=3.22 language: cpp compiler: # - clang - gcc script: make plv8/plv8.sql.common0000664000401600040160000000161312704526555012726 0ustar cbecbe#include "pg_config.h" #if PG_VERSION_NUM < 90100 -- adjust this setting to control where the objects get created. SET search_path = public; BEGIN; #endif CREATE FUNCTION @LANG_NAME@_call_handler() RETURNS language_handler AS 'MODULE_PATHNAME' LANGUAGE C; #if PG_VERSION_NUM >= 90000 CREATE FUNCTION @LANG_NAME@_inline_handler(internal) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; #endif CREATE FUNCTION @LANG_NAME@_call_validator(oid) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C; CREATE TRUSTED LANGUAGE @LANG_NAME@ HANDLER @LANG_NAME@_call_handler #if PG_VERSION_NUM >= 90000 INLINE @LANG_NAME@_inline_handler #endif VALIDATOR @LANG_NAME@_call_validator; #ifdef LANG_plv8 CREATE DOMAIN plv8_int2array AS int2[]; CREATE DOMAIN plv8_int4array AS int4[]; CREATE DOMAIN plv8_float4array AS float4[]; CREATE DOMAIN plv8_float8array AS float8[]; #endif #if PG_VERSION_NUM < 90100 COMMIT; #endif plv8/Makefile.v80000664000401600040160000000311113064535725012013 0ustar cbecbe#-----------------------------------------------------------------------------# # # Makefile for v8 static link # # 'make static' will download the v8 source and build it, then build plv8 # with statically link to v8 with snapshot. This assumes certain directory # structure in v8 which may be different from version to another, but user # can specify the v8 version by AUTOV8_VERSION, too. #-----------------------------------------------------------------------------# AUTOV8_VERSION = 3.14.5 AUTOV8_DIR = build/v8-$(AUTOV8_VERSION) AUTOV8_OUT = build/v8-$(AUTOV8_VERSION)/out/native AUTOV8_LIB = $(AUTOV8_OUT)/libv8_snapshot.a AUTOV8_STATIC_LIBS = libv8_base.a libv8_snapshot.a SHLIB_LINK += $(addprefix $(AUTOV8_OUT)/, $(AUTOV8_STATIC_LIBS)) all: $(AUTOV8_LIB) # For some reason, this solves parallel make dependency. plv8_config.h: $(AUTOV8_LIB) $(AUTOV8_DIR): mkdir -p build curl -L 'https://github.com/v8/v8/archive/$(AUTOV8_VERSION).tar.gz' \ | tar zxf - -C build $(AUTOV8_LIB): $(AUTOV8_DIR) env CXXFLAGS=-fPIC $(MAKE) -C build/v8-$(AUTOV8_VERSION) dependencies env CXXFLAGS=-fPIC $(MAKE) -C build/v8-$(AUTOV8_VERSION) native test -f $(AUTOV8_OUT)/libv8_base.a || \ ln -s $(abspath $(AUTOV8_OUT)/obj.target/tools/gyp/libv8_base.a) \ $(AUTOV8_OUT)/libv8_base.a test -f $(AUTOV8_OUT)/libv8_snapshot.a || \ ln -s $(abspath $(AUTOV8_OUT)/obj.target/tools/gyp/libv8_snapshot.a) \ $(AUTOV8_OUT)/libv8_snapshot.a include Makefile CCFLAGS += -I$(AUTOV8_DIR)/include # We're gonna build static link. Rip it out after include Makefile SHLIB_LINK := $(filter-out -lv8, $(SHLIB_LINK)) plv8/package.sh0000775000401600040160000000124312704526555011756 0ustar cbecbe#!/bin/sh ORIG=`pwd` V8DIR=`dirname $0` if [ -z $1 ]; then echo "usage: $0 tagname" exit fi TAG=$1 cd $V8DIR GITHEAD=`git symbolic-ref --short HEAD` echo GITHEAD = $GITHEAD git checkout $TAG if [ $? -ne 0 ]; then echo "failed to checkout: $TAG" exit fi V8VER=`v8 -e 'print(JSON.parse(read("META.json")).version)' 2>/dev/null` if [ -z "$V8VER" ] ; then V8VER=`d8 -e 'print(JSON.parse(read("META.json")).version)'` fi if [ -z "$V8VER" ] ; then echo "Could not get version for plv8" exit 1 fi git archive --output $ORIG/plv8-$V8VER.zip \ --prefix=plv8-$V8VER/ \ --format=zip \ $TAG git checkout $GITHEAD plv8/plv8_func.cc0000664000401600040160000007766413073210527012250 0ustar cbecbe/*------------------------------------------------------------------------- * * plv8_func.cc : PL/v8 built-in functions. * * Copyright (c) 2009-2012, the PLV8JS Development Group. *------------------------------------------------------------------------- */ #include "plv8.h" #include "plv8_param.h" #include #include extern "C" { #include "access/xact.h" #include "catalog/pg_type.h" #include "executor/spi.h" #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #undef delete #undef namespace #undef typeid #undef typename #undef using } // extern "C" using namespace v8; static Handle plv8_FunctionInvoker(const Arguments& args) throw(); static Handle plv8_Elog(const Arguments& args); static Handle plv8_Execute(const Arguments& args); static Handle plv8_Prepare(const Arguments& args); static Handle plv8_PlanCursor(const Arguments& args); static Handle plv8_PlanExecute(const Arguments& args); static Handle plv8_PlanFree(const Arguments& args); static Handle plv8_CursorFetch(const Arguments& args); static Handle plv8_CursorMove(const Arguments& args); static Handle plv8_CursorClose(const Arguments& args); static Handle plv8_ReturnNext(const Arguments& args); static Handle plv8_Subtransaction(const Arguments& args); static Handle plv8_FindFunction(const Arguments& args); static Handle plv8_GetWindowObject(const Arguments& args); static Handle plv8_WinGetPartitionLocal(const Arguments& args); static Handle plv8_WinSetPartitionLocal(const Arguments& args); static Handle plv8_WinGetCurrentPosition(const Arguments& args); static Handle plv8_WinGetPartitionRowCount(const Arguments& args); static Handle plv8_WinSetMarkPosition(const Arguments& args); static Handle plv8_WinRowsArePeers(const Arguments& args); static Handle plv8_WinGetFuncArgInPartition(const Arguments& args); static Handle plv8_WinGetFuncArgInFrame(const Arguments& args); static Handle plv8_WinGetFuncArgCurrent(const Arguments& args); static Handle plv8_QuoteLiteral(const Arguments& args); static Handle plv8_QuoteNullable(const Arguments& args); static Handle plv8_QuoteIdent(const Arguments& args); /* * Window function API allows to store partition-local memory, but * the allocation is only once per partition. maxlen represents * the allocated size for this partition (if it's zero, the allocation * has just happened). Also v8 doesn't provide vaule serialization, * so currently the object is JSON-ized and stored as a string. */ typedef struct window_storage { size_t maxlen; /* allocated memory */ size_t len; /* the byte size of data */ char data[1]; /* actual string (without null-termination */ } window_storage; #if PG_VERSION_NUM < 90100 /* * quote_literal_cstr - * returns a properly quoted literal */ static char * quote_literal_cstr(const char *rawstr) { return TextDatumGetCString( DirectFunctionCall1(quote_literal, CStringGetTextDatum(rawstr))); } #endif static inline Local WrapCallback(InvocationCallback func) { return External::New( reinterpret_cast( reinterpret_cast(func))); } static inline InvocationCallback UnwrapCallback(Handle value) { return reinterpret_cast( reinterpret_cast(External::Cast(*value)->Value())); } static inline void SetCallback(Handle obj, const char *name, InvocationCallback func, PropertyAttribute attr = None) { obj->Set(String::NewSymbol(name), FunctionTemplate::New(plv8_FunctionInvoker, WrapCallback(func)), attr); } class SubTranBlock { private: ResourceOwner m_resowner; MemoryContext m_mcontext; public: SubTranBlock(); void enter(); void exit(bool success); }; Persistent PlanTemplate; Persistent CursorTemplate; Persistent WindowObjectTemplate; static Handle SPIResultToValue(int status) { Local result; if (status < 0) return ThrowError(FormatSPIStatus(status)); 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); Local rows = Array::New(nrows); for (int r = 0; r < nrows; r++) rows->Set(r, conv.ToValue(SPI_tuptable->vals[r])); result = rows; break; } default: result = Int32::New(SPI_processed); break; } return result; } SubTranBlock::SubTranBlock() {} void SubTranBlock::enter() { if (!IsTransactionOrTransactionBlock()) throw js_error("out of transaction"); m_resowner = CurrentResourceOwner; m_mcontext = CurrentMemoryContext; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(m_mcontext); } void SubTranBlock::exit(bool success) { if (success) ReleaseCurrentSubTransaction(); else RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(m_mcontext); CurrentResourceOwner = m_resowner; } JSONObject::JSONObject() { Handle context = Context::GetCurrent(); Handle global = context->Global(); m_json = global->Get(String::NewSymbol("JSON"))->ToObject(); if (m_json.IsEmpty()) throw js_error("JSON not found"); } /* * Call JSON.parse(). Currently this supports only one argument. */ Handle JSONObject::Parse(Handle str) { Handle parse_func = Handle::Cast(m_json->Get(String::NewSymbol("parse"))); if (parse_func.IsEmpty()) throw js_error("JSON.parse() not found"); return parse_func->Call(m_json, 1, &str); } /* * Call JSON.stringify(). Currently this supports only one argument. */ Handle JSONObject::Stringify(Handle val) { Handle stringify_func = Handle::Cast(m_json->Get(String::NewSymbol("stringify"))); if (stringify_func.IsEmpty()) throw js_error("JSON.stringify() not found"); return stringify_func->Call(m_json, 1, &val); } void SetupPlv8Functions(Handle plv8) { PropertyAttribute attrFull = PropertyAttribute(ReadOnly | DontEnum | DontDelete); SetCallback(plv8, "elog", plv8_Elog, attrFull); SetCallback(plv8, "execute", plv8_Execute, attrFull); SetCallback(plv8, "prepare", plv8_Prepare, attrFull); SetCallback(plv8, "return_next", plv8_ReturnNext, attrFull); SetCallback(plv8, "subtransaction", plv8_Subtransaction, attrFull); SetCallback(plv8, "find_function", plv8_FindFunction, attrFull); SetCallback(plv8, "get_window_object", plv8_GetWindowObject, attrFull); SetCallback(plv8, "quote_literal", plv8_QuoteLiteral, attrFull); SetCallback(plv8, "quote_nullable", plv8_QuoteNullable, attrFull); SetCallback(plv8, "quote_ident", plv8_QuoteIdent, attrFull); plv8->SetInternalFieldCount(PLV8_INTNL_MAX); } /* * 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 plv8_FunctionInvoker(const Arguments &args) throw() { HandleScope handle_scope; MemoryContext ctx = CurrentMemoryContext; InvocationCallback fn = UnwrapCallback(args.Data()); try { return fn(args); } catch (js_error& e) { return ThrowException(e.error_object()); } catch (pg_error& e) { MemoryContextSwitchTo(ctx); ErrorData *edata = CopyErrorData(); Handle message = ToString(edata->message); // XXX: add other fields? (detail, hint, context, internalquery...) FlushErrorState(); FreeErrorData(edata); return ThrowException(Exception::Error(message)); } } /* * plv8.elog(elevel, str) */ static Handle plv8_Elog(const Arguments& args) { MemoryContext ctx = CurrentMemoryContext; if (args.Length() < 2) return ThrowError("usage: plv8.elog(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: case ERROR: break; default: return ThrowError("invalid error level"); } std::string msg; std::string buf; for (int i = 1; i < args.Length(); i++) { if (i > 1){ msg += " "; } if (!CString::toStdString(args[i],buf)){ return Undefined(); } msg += buf; } const char *message = msg.c_str(); if (elevel != ERROR) { elog(elevel, "%s", message); return Undefined(); } /* ERROR case */ PG_TRY(); { elog(elevel, "%s", message); } PG_CATCH(); { MemoryContextSwitchTo(ctx); ErrorData *edata = CopyErrorData(); Local message = ToString(edata->message); FlushErrorState(); FreeErrorData(edata); return ThrowException(Exception::Error(message)); } PG_END_TRY(); return Undefined(); } static Datum value_get_datum(Handle value, Oid typid, char *isnull) { if (value->IsUndefined() || value->IsNull()) { *isnull = 'n'; return (Datum) 0; } else { plv8_type typinfo = { 0 }; bool IsNull; Datum datum; plv8_fill_type(&typinfo, typid); try { datum = ToDatum(value, &IsNull, &typinfo); } catch (js_error& e){ e.rethrow(); } catch (pg_error& e){ e.rethrow(); } *isnull = (IsNull ? 'n' : ' '); return datum; } } static int plv8_execute_params(const char *sql, Handle params) { Assert(!params.IsEmpty()); int status; int nparam = params->Length(); Datum *values = (Datum *) palloc(sizeof(Datum) * nparam); char *nulls = (char *) palloc(sizeof(char) * nparam); /* * Since 9.0, SPI may have the parser deduce the parameter types. In prior * versions, we infer the types from the input JS values. */ #if PG_VERSION_NUM >= 90000 SPIPlanPtr plan; plv8_param_state parstate = {0}; ParamListInfo paramLI; parstate.memcontext = CurrentMemoryContext; plan = SPI_prepare_params(sql, plv8_variable_param_setup, &parstate, 0); if (parstate.numParams != nparam) elog(ERROR, "parameter numbers mismatch: %d != %d", parstate.numParams, nparam); for (int i = 0; i < nparam; i++) { Handle param = params->Get(i); values[i] = value_get_datum(param, parstate.paramTypes[i], &nulls[i]); } paramLI = plv8_setup_variable_paramlist(&parstate, values, nulls); status = SPI_execute_plan_with_paramlist(plan, paramLI, false, 0); #else Oid *types = (Oid *) palloc(sizeof(Oid) * nparam); for (int i = 0; i < nparam; i++) { Handle param = params->Get(i); types[i] = inferred_datum_type(param); if (types[i] == InvalidOid) elog(ERROR, "parameter[%d] cannot translate to a database type", i); values[i] = value_get_datum(param, types[i], &nulls[i]); } status = SPI_execute_with_args(sql, nparam, types, values, nulls, false, 0); pfree(types); #endif pfree(values); pfree(nulls); return status; } static Handle convertArgsToArray(const Arguments &args, int start, int downshift) { Local result = Array::New(args.Length() - start); for (int i = start; i < args.Length(); i++) { result->Set(i - downshift, args[i]); } return result; } /* * plv8.execute(statement, [param, ...]) */ static Handle plv8_Execute(const Arguments &args) { int status; if (args.Length() < 1) return Undefined(); CString sql(args[0]); Handle params; if (args.Length() >= 2) { if (args[1]->IsArray()) params = Handle::Cast(args[1]); else /* Consume trailing elements as an array. */ params = convertArgsToArray(args, 1, 1); } int nparam = params.IsEmpty() ? 0 : params->Length(); SubTranBlock subtran; PG_TRY(); { subtran.enter(); if (nparam == 0) status = SPI_exec(sql, 0); else status = plv8_execute_params(sql, params); } PG_CATCH(); { subtran.exit(false); SPI_pop_conditional(true); throw pg_error(); } PG_END_TRY(); subtran.exit(true); return SPIResultToValue(status); } /* * plv8.prepare(statement, args...) */ static Handle plv8_Prepare(const Arguments &args) { SPIPlanPtr initial = NULL, saved; CString sql(args[0]); Handle array; int arraylen = 0; Oid *types = NULL; plv8_param_state *parstate = NULL; if (args.Length() > 1) { if (args[1]->IsArray()) array = Handle::Cast(args[1]); else /* Consume trailing elements as an array. */ array = convertArgsToArray(args, 1, 0); arraylen = array->Length(); types = (Oid *) palloc(sizeof(Oid) * arraylen); } for (int i = 0; i < arraylen; i++) { CString typestr(array->Get(i)); int32 typemod; #if PG_VERSION_NUM >= 90400 parseTypeString(typestr, &types[i], &typemod, false); #else parseTypeString(typestr, &types[i], &typemod); #endif } PG_TRY(); { #if PG_VERSION_NUM >= 90000 if (args.Length() == 1) { parstate = (plv8_param_state *) palloc0(sizeof(plv8_param_state)); parstate->memcontext = CurrentMemoryContext; initial = SPI_prepare_params(sql, plv8_variable_param_setup, parstate, 0); } else #endif initial = SPI_prepare(sql, arraylen, types); saved = SPI_saveplan(initial); SPI_freeplan(initial); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); if (PlanTemplate.IsEmpty()) { Local base = FunctionTemplate::New(); base->SetClassName(String::NewSymbol("PreparedPlan")); Local templ = base->InstanceTemplate(); templ->SetInternalFieldCount(2); SetCallback(templ, "cursor", plv8_PlanCursor); SetCallback(templ, "execute", plv8_PlanExecute); SetCallback(templ, "free", plv8_PlanFree); PlanTemplate = Persistent::New(templ); } Local result = PlanTemplate->NewInstance(); result->SetInternalField(0, External::New(saved)); result->SetInternalField(1, External::New(parstate)); return result; } /* * plan.cursor(args, ...) */ static Handle plv8_PlanCursor(const Arguments &args) { Handle self = args.This(); SPIPlanPtr plan; Datum *values = NULL; char *nulls = NULL; int nparam = 0, argcount; Handle params; Portal cursor; plv8_param_state *parstate = NULL; plan = static_cast( Handle::Cast(self->GetInternalField(0))->Value()); /* XXX: Add plan validation */ if (args.Length() > 0) { if (args[0]->IsArray()) params = Handle::Cast(args[0]); else params = convertArgsToArray(args, 0, 0); nparam = params->Length(); } /* * If the plan has the variable param info, use it. */ parstate = static_cast( Handle::Cast(self->GetInternalField(1))->Value()); if (parstate) argcount = parstate->numParams; else argcount = SPI_getargcount(plan); if (argcount != nparam) { StringInfoData buf; initStringInfo(&buf); appendStringInfo(&buf, "plan expected %d argument(s), given is %d", argcount, nparam); throw js_error(pstrdup(buf.data)); } if (nparam > 0) { values = (Datum *) palloc(sizeof(Datum) * nparam); nulls = (char *) palloc(sizeof(char) * nparam); } for (int i = 0; i < nparam; i++) { Handle param = params->Get(i); Oid typid; if (parstate) typid = parstate->paramTypes[i]; else typid = SPI_getargtypeid(plan, i); values[i] = value_get_datum(param, typid, &nulls[i]); } PG_TRY(); { #if PG_VERSION_NUM >= 90000 if (parstate) { ParamListInfo paramLI; paramLI = plv8_setup_variable_paramlist(parstate, values, nulls); cursor = SPI_cursor_open_with_paramlist(NULL, plan, paramLI, false); } else #endif cursor = SPI_cursor_open(NULL, plan, values, nulls, false); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); Handle cname = ToString(cursor->name, strlen(cursor->name)); /* * Instantiate if the template is empty. */ if (CursorTemplate.IsEmpty()) { Local base = FunctionTemplate::New(); base->SetClassName(String::NewSymbol("Cursor")); Local templ = base->InstanceTemplate(); templ->SetInternalFieldCount(1); SetCallback(templ, "fetch", plv8_CursorFetch); SetCallback(templ, "move", plv8_CursorMove); SetCallback(templ, "close", plv8_CursorClose); CursorTemplate = Persistent::New(templ); } Local result = CursorTemplate->NewInstance(); result->SetInternalField(0, cname); return result; } /* * plan.execute(args, ...) */ static Handle plv8_PlanExecute(const Arguments &args) { Handle self = args.This(); SPIPlanPtr plan; Datum *values = NULL; char *nulls = NULL; int nparam = 0, argcount; Handle params; SubTranBlock subtran; int status; plv8_param_state *parstate = NULL; plan = static_cast( Handle::Cast(self->GetInternalField(0))->Value()); /* XXX: Add plan validation */ if (args.Length() > 0) { if (args[0]->IsArray()) params = Handle::Cast(args[0]); else params = convertArgsToArray(args, 0, 0); nparam = params->Length(); } /* * If the plan has the variable param info, use it. */ parstate = static_cast( Handle::Cast(self->GetInternalField(1))->Value()); if (parstate) argcount = parstate->numParams; else argcount = SPI_getargcount(plan); if (argcount != nparam) { StringInfoData buf; initStringInfo(&buf); appendStringInfo(&buf, "plan expected %d argument(s), given is %d", argcount, nparam); throw js_error(pstrdup(buf.data)); } if (nparam > 0) { values = (Datum *) palloc(sizeof(Datum) * nparam); nulls = (char *) palloc(sizeof(char) * nparam); } for (int i = 0; i < nparam; i++) { Handle param = params->Get(i); Oid typid; if (parstate) typid = parstate->paramTypes[i]; else typid = SPI_getargtypeid(plan, i); values[i] = value_get_datum(param, typid, &nulls[i]); } PG_TRY(); { subtran.enter(); #if PG_VERSION_NUM >= 90000 if (parstate) { ParamListInfo paramLI; paramLI = plv8_setup_variable_paramlist(parstate, values, nulls); status = SPI_execute_plan_with_paramlist(plan, paramLI, false, 0); } else #endif status = SPI_execute_plan(plan, values, nulls, false, 0); } PG_CATCH(); { subtran.exit(false); throw pg_error(); } PG_END_TRY(); subtran.exit(true); return SPIResultToValue(status); } /* * plan.free() */ static Handle plv8_PlanFree(const Arguments &args) { Handle self = args.This(); SPIPlanPtr plan; plv8_param_state *parstate; int status = 0; plan = static_cast( Handle::Cast(self->GetInternalField(0))->Value()); if (plan) status = SPI_freeplan(plan); self->SetInternalField(0, External::New(0)); parstate = static_cast( Handle::Cast(self->GetInternalField(1))->Value()); if (parstate) pfree(parstate); self->SetInternalField(1, External::New(0)); return Int32::New(status); } /* * cursor.fetch([n]) */ static Handle plv8_CursorFetch(const Arguments &args) { Handle self = args.This(); CString cname(self->GetInternalField(0)); Portal cursor = SPI_cursor_find(cname); int nfetch = 1; bool forward = true, wantarray = false; if (!cursor) throw js_error("cannot find cursor"); if (args.Length() >= 1) { wantarray = true; nfetch = args[0]->Int32Value(); if (nfetch < 0) { nfetch = -nfetch; forward = false; } } PG_TRY(); { SPI_cursor_fetch(cursor, forward, nfetch); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); if (SPI_processed > 0) { Converter conv(SPI_tuptable->tupdesc); if (!wantarray) { Handle result = conv.ToValue(SPI_tuptable->vals[0]); return result; } else { Handle array = Array::New(); for (unsigned int i = 0; i < SPI_processed; i++) array->Set(i, conv.ToValue(SPI_tuptable->vals[i])); return array; } } return Undefined(); } /* * cursor.move(n) */ static Handle plv8_CursorMove(const Arguments& args) { Handle self = args.This(); CString cname(self->GetInternalField(0)); Portal cursor = SPI_cursor_find(cname); int nmove = 1; bool forward = true; if (!cursor) throw js_error("cannot find cursor"); if (args.Length() < 1) return Undefined(); nmove = args[0]->Int32Value(); if (nmove < 0) { nmove = -nmove; forward = false; } PG_TRY(); { SPI_cursor_move(cursor, forward, nmove); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return Undefined(); } /* * cursor.close() */ static Handle plv8_CursorClose(const Arguments &args) { Handle self = args.This(); CString cname(self->GetInternalField(0)); Portal cursor = SPI_cursor_find(cname); if (!cursor) throw js_error("cannot find cursor"); PG_TRY(); { SPI_cursor_close(cursor); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return Int32::New(cursor ? 1 : 0); } /* * plv8.return_next(retval) */ static Handle plv8_ReturnNext(const Arguments& args) { Handle self = args.This(); Handle conv_value = self->GetInternalField(PLV8_INTNL_CONV); if (!conv_value->IsExternal()) throw js_error("return_next called in context that cannot accept a set"); Converter *conv = static_cast( Handle::Cast(conv_value)->Value()); Tuplestorestate *tupstore = static_cast( Handle::Cast( self->GetInternalField(PLV8_INTNL_TUPSTORE))->Value()); conv->ToDatum(args[0], tupstore); return Undefined(); } /* * plv8.subtransaction(func(){ ... }) */ static Handle plv8_Subtransaction(const Arguments& args) { if (args.Length() < 1) return Undefined(); if (!args[0]->IsFunction()) return Undefined(); Handle func = Handle::Cast(args[0]); SubTranBlock subtran; subtran.enter(); Handle emptyargs[] = {}; TryCatch try_catch; Handle result = func->Call(func, 0, emptyargs); subtran.exit(!result.IsEmpty()); if (result.IsEmpty()) throw js_error(try_catch); return result; } /* * plv8.find_function("signature") */ static Handle plv8_FindFunction(const Arguments& args) { if (args.Length() < 1) return Undefined(); CString signature(args[0]); Local func; PG_TRY(); { func = find_js_function_by_name(signature.str()); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return func; } /* * plv8.get_window_object() * Returns window object in window functions, which provides window function API. */ static Handle plv8_GetWindowObject(const Arguments& args) { Handle self = args.This(); Handle fcinfo_value = self->GetInternalField(PLV8_INTNL_FCINFO); if (!fcinfo_value->IsExternal()) throw js_error("get_window_object called in wrong context"); if (WindowObjectTemplate.IsEmpty()) { /* Initialize it if we haven't yet. */ Local base = FunctionTemplate::New(); base->SetClassName(String::NewSymbol("WindowObject")); Local templ = base->InstanceTemplate(); /* We store fcinfo here. */ templ->SetInternalFieldCount(1); /* Functions. */ SetCallback(templ, "get_partition_local", plv8_WinGetPartitionLocal); SetCallback(templ, "set_partition_local", plv8_WinSetPartitionLocal); SetCallback(templ, "get_current_position", plv8_WinGetCurrentPosition); SetCallback(templ, "get_partition_row_count", plv8_WinGetPartitionRowCount); SetCallback(templ, "set_mark_position", plv8_WinSetMarkPosition); SetCallback(templ, "rows_are_peers", plv8_WinRowsArePeers); SetCallback(templ, "get_func_arg_in_partition", plv8_WinGetFuncArgInPartition); SetCallback(templ, "get_func_arg_in_frame", plv8_WinGetFuncArgInFrame); SetCallback(templ, "get_func_arg_current", plv8_WinGetFuncArgCurrent); /* Constants for get_func_in_XXX() */ templ->Set(String::NewSymbol("SEEK_CURRENT"), Int32::New(WINDOW_SEEK_CURRENT)); templ->Set(String::NewSymbol("SEEK_HEAD"), Int32::New(WINDOW_SEEK_HEAD)); templ->Set(String::NewSymbol("SEEK_TAIL"), Int32::New(WINDOW_SEEK_TAIL)); WindowObjectTemplate = Persistent::New(templ); } Local js_winobj = WindowObjectTemplate->NewInstance(); js_winobj->SetInternalField(0, fcinfo_value); return js_winobj; } /* * Short-cut routine for window function API */ static inline WindowObject plv8_MyWindowObject(const Arguments& args) { Handle self = args.This(); /* fcinfo is embedded in the internal field. See plv8_GetWindowObject() */ FunctionCallInfo fcinfo = static_cast( Handle::Cast(self->GetInternalField(0))->Value()); if (fcinfo == NULL) throw js_error("window function api called with wrong object"); WindowObject winobj = PG_WINDOW_OBJECT(); if (!winobj) throw js_error("window function api called with wrong object"); return winobj; } /* * Short-cut routine for window function API * Unfortunately, in the JS functino level we don't know the plv8 function * argument information enough. Thus, we obtain it from function expression. */ static inline plv8_type * plv8_MyArgType(const Arguments& args, int argno) { Handle self = args.This(); FunctionCallInfo fcinfo = static_cast( Handle::Cast(self->GetInternalField(0))->Value()); if (fcinfo == NULL) throw js_error("window function api called with wrong object"); /* This is safe to call in C++ context (without PG_TRY). */ return get_plv8_type(fcinfo, argno); } /* * winobj.get_partition_local([size]) * The default allocation size is 1K, but the caller can override this value * by the argument at the first call. */ static Handle plv8_WinGetPartitionLocal(const Arguments& args) { WindowObject winobj = plv8_MyWindowObject(args); size_t size; window_storage *storage; if (args.Length() < 1) size = 1000; /* default 1K */ else size = args[0]->Int32Value(); size += sizeof(size_t) * 2; PG_TRY(); { storage = (window_storage *) WinGetPartitionLocalMemory(winobj, size); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); /* If it's new, store the maximum size. */ if (storage->maxlen == 0) storage->maxlen = size; /* If nothing is stored, undefined is returned. */ if (storage->len == 0) return Undefined(); /* * Currently we support only serializable JSON object to be stored. */ JSONObject JSON; Handle value = ToString(storage->data, storage->len); return JSON.Parse(value); } /* * winobj.set_partition_local(obj) * If the storage has not been allocated, it's allocated based on the * size of JSON-ized input string. */ static Handle plv8_WinSetPartitionLocal(const Arguments& args) { WindowObject winobj = plv8_MyWindowObject(args); if (args.Length() < 1) return Undefined(); JSONObject JSON; Handle value = JSON.Stringify(args[0]); CString str(value); size_t str_size = strlen(str); size_t size = str_size + sizeof(size_t) * 2; window_storage *storage; PG_TRY(); { storage = (window_storage *) WinGetPartitionLocalMemory(winobj, size); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); if (storage->maxlen != 0 && storage->maxlen < size) { throw js_error("window local memory overflow"); } else if (storage->maxlen == 0) { /* new allocation */ storage->maxlen = size; } storage->len = str_size; memcpy(storage->data, str, str_size); return Undefined(); } /* * winobj.get_current_position() */ static Handle plv8_WinGetCurrentPosition(const Arguments& args) { WindowObject winobj = plv8_MyWindowObject(args); int64 pos = 0; PG_TRY(); { pos = WinGetCurrentPosition(winobj); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return Integer::New(pos); } /* * winobj.get_partition_row_count() */ static Handle plv8_WinGetPartitionRowCount(const Arguments& args) { WindowObject winobj = plv8_MyWindowObject(args); int64 pos = 0; PG_TRY(); { pos = WinGetPartitionRowCount(winobj); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return Integer::New(pos); } /* * winobj.set_mark_pos(pos) */ static Handle plv8_WinSetMarkPosition(const Arguments& args) { WindowObject winobj = plv8_MyWindowObject(args); if (args.Length() < 1) return Undefined(); int64 markpos = args[0]->IntegerValue(); PG_TRY(); { WinSetMarkPosition(winobj, markpos); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return Undefined(); } /* * winobj.rows_are_peers(pos1, pos2) */ static Handle plv8_WinRowsArePeers(const Arguments& args) { WindowObject winobj = plv8_MyWindowObject(args); if (args.Length() < 2) return Undefined(); int64 pos1 = args[0]->IntegerValue(); int64 pos2 = args[1]->IntegerValue(); bool res = false; PG_TRY(); { res = WinRowsArePeers(winobj, pos1, pos2); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return Boolean::New(res); } /* * winobj.get_func_arg_in_partition(argno, relpos, seektype, set_mark) */ static Handle plv8_WinGetFuncArgInPartition(const Arguments& args) { WindowObject winobj = plv8_MyWindowObject(args); /* Since we return undefined in "isout" case, throw if arg isn't enough. */ if (args.Length() < 4) throw js_error("argument not enough"); int argno = args[0]->Int32Value(); int relpos = args[1]->Int32Value(); int seektype = args[2]->Int32Value(); bool set_mark = args[3]->BooleanValue(); bool isnull, isout; Datum res; PG_TRY(); { res = WinGetFuncArgInPartition(winobj, argno, relpos, seektype, set_mark, &isnull, &isout); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); /* Return undefined to tell it's out of partition. */ if (isout) return Undefined(); plv8_type *type = plv8_MyArgType(args, argno); return ToValue(res, isnull, type); } /* * winobj.get_func_arg_in_frame(argno, relpos, seektype, set_mark) */ static Handle plv8_WinGetFuncArgInFrame(const Arguments& args) { WindowObject winobj = plv8_MyWindowObject(args); /* Since we return undefined in "isout" case, throw if arg isn't enough. */ if (args.Length() < 4) throw js_error("argument not enough"); int argno = args[0]->Int32Value(); int relpos = args[1]->Int32Value(); int seektype = args[2]->Int32Value(); bool set_mark = args[3]->BooleanValue(); bool isnull, isout; Datum res; PG_TRY(); { res = WinGetFuncArgInFrame(winobj, argno, relpos, seektype, set_mark, &isnull, &isout); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); /* Return undefined to tell it's out of frame. */ if (isout) return Undefined(); plv8_type *type = plv8_MyArgType(args, argno); return ToValue(res, isnull, type); } /* * winobj.get_func_arg_current(argno) */ static Handle plv8_WinGetFuncArgCurrent(const Arguments& args) { WindowObject winobj = plv8_MyWindowObject(args); if (args.Length() < 1) return Undefined(); int argno = args[0]->Int32Value(); bool isnull; Datum res; PG_TRY(); { res = WinGetFuncArgCurrent(winobj, argno, &isnull); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); plv8_type *type = plv8_MyArgType(args, argno); return ToValue(res, isnull, type); } /* * plv8.quote_literal(str) */ static Handle plv8_QuoteLiteral(const Arguments& args) { if (args.Length() < 1) return Undefined(); CString instr(args[0]); char *result; PG_TRY(); { result = quote_literal_cstr(instr); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return ToString(result); } /* * plv8.quote_nullable(str) */ static Handle plv8_QuoteNullable(const Arguments& args) { if (args.Length() < 1) return Undefined(); CString instr(args[0]); char *result; if (args[0]->IsNull() || args[0]->IsUndefined()) return ToString("NULL"); PG_TRY(); { result = quote_literal_cstr(instr); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return ToString(result); } /* * plv8.quote_ident(str) */ static Handle plv8_QuoteIdent(const Arguments& args) { if (args.Length() < 1) return Undefined(); CString instr(args[0]); const char *result; PG_TRY(); { result = quote_identifier(instr); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return ToString(result); } plv8/plv8.control.common0000664000401600040160000000067312704526555013614 0ustar cbecbe#if defined LANG_plv8 ## plv8 extension comment = 'PL/JavaScript (v8) trusted procedural language' #elif defined LANG_plcoffee ## plcoffee extension comment = 'PL/CoffeeScript (v8) trusted procedural language' #elif defined LANG_plls ## plls extension comment = 'PL/LiveScript (v8) trusted procedural language' #endif default_version = '@PLV8_VERSION@' module_pathname = '$libdir/plv8' relocatable = false schema = pg_catalog superuser = true plv8/expected/0000775000401600040160000000000013073210527011612 5ustar cbecbeplv8/expected/window.out0000664000401600040160000003006013064535725013663 0ustar cbecbe-- window functions CREATE FUNCTION js_row_number() RETURNS int8 AS $$ var winobj = plv8.get_window_object(); return winobj.get_current_position() + 1; $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION __js_rank_up(winobj internal, up_callback internal) RETURNS void AS $$ var context = winobj.get_partition_local() || {}; var pos = winobj.get_current_position(); context.up = false; if (!context.rank) { context.rank = 1; } else { if (!winobj.rows_are_peers(pos, pos - 1)) { context.up = true; if (up_callback) { up_callback(context); } } } winobj.set_mark_position(pos); winobj.set_partition_local(context); return context; $$ LANGUAGE plv8; CREATE FUNCTION js_rank() RETURNS int8 AS $$ var winobj = plv8.get_window_object(); var context = plv8.find_function("__js_rank_up")(winobj, function(context){ context.rank = winobj.get_current_position() + 1; }); return context.rank; $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_dense_rank() RETURNS int8 AS $$ var winobj = plv8.get_window_object(); var context = plv8.find_function("__js_rank_up")(winobj, function(context){ context.rank++; }); return context.rank; $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_percent_rank() RETURNS float AS $$ var winobj = plv8.get_window_object(); var totalrows = winobj.get_partition_row_count(); if (totalrows <= 1) return 0.0; var context = plv8.find_function("__js_rank_up")(winobj, function(context){ context.rank = winobj.get_current_position() + 1; }); return (context.rank - 1) / (totalrows - 1); $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_cume_dist() RETURNS float AS $$ var winobj = plv8.get_window_object(); var totalrows = winobj.get_partition_row_count(); var context = plv8.find_function("__js_rank_up")(winobj); if (context.up || context.rank == 1) { context.rank = winobj.get_current_position() + 1; for (var row = context.rank; row < totalrows; row++) { if (!winobj.rows_are_peers(row - 1, row)) { break; } context.rank++; } } winobj.set_partition_local(context); return context.rank / totalrows; $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_ntile(nbuckets int8) RETURNS int AS $$ var winobj = plv8.get_window_object(); var context = winobj.get_partition_local() || {}; if (!context.ntile) { context.rows_per_bucket = 0; var total = winobj.get_partition_row_count(); var nbuckets = winobj.get_func_arg_current(0); if (nbuckets === null) { return null; } if (nbuckets <= 0) { plv8.elog(ERROR, "argument of ntile must be greater than zero"); } context.ntile = 1; context.rows_per_bucket = 0; context.boundary = total / nbuckets; if (context.boundary <= 0) { context.boundary = 1; } else { context.remainder = total % nbuckets; if (context.remainder != 0) { context.boundary++; } } } context.rows_per_bucket++; if (context.boundary < context.rows_per_bucket) { if (context.remainder != 0 && context.ntile == context.remainder) { context.remainder = 0; context.boundary -= 1; } context.ntile += 1; context.rows_per_bucket = 1; } winobj.set_partition_local(context); return context.ntile; $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION __js_lead_lag_common(forward internal, withoffset internal, withdefault internal) RETURNS void AS $$ var winobj = plv8.get_window_object(); var offset; if (withoffset) { offset = winobj.get_func_arg_current(1); if (offset === null) { return null; } } else { offset = 1; } var result = winobj.get_func_arg_in_partition(0, forward ? offset : -offset, winobj.SEEK_CURRENT, false); if (result === undefined) { if (withdefault) { result = winobj.get_func_arg_current(2); } } if (result === null) { return null; } return result; $$ LANGUAGE plv8; CREATE FUNCTION js_lag(arg anyelement) RETURNS anyelement AS $$ return plv8.find_function("__js_lead_lag_common")(false, false, false); $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_lag(arg anyelement, ofs int) RETURNS anyelement AS $$ return plv8.find_function("__js_lead_lag_common")(false, true, false); $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_lag(arg anyelement, ofs int, deflt anyelement) RETURNS anyelement AS $$ return plv8.find_function("__js_lead_lag_common")(false, true, true); $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_lead(arg anyelement) RETURNS anyelement AS $$ return plv8.find_function("__js_lead_lag_common")(true, false, false); $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_lead(arg anyelement, ofs int) RETURNS anyelement AS $$ return plv8.find_function("__js_lead_lag_common")(true, true, false); $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_lead(arg anyelement, ofs int, deflt anyelement) RETURNS anyelement AS $$ return plv8.find_function("__js_lead_lag_common")(true, true, true); $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_first_value(arg anyelement) RETURNS anyelement AS $$ var winobj = plv8.get_window_object(); return winobj.get_func_arg_in_frame(0, 0, winobj.SEEK_HEAD, true); $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_last_value(arg anyelement) RETURNS anyelement AS $$ var winobj = plv8.get_window_object(); return winobj.get_func_arg_in_frame(0, 0, winobj.SEEK_TAIL, true); $$ LANGUAGE plv8 WINDOW; CREATE FUNCTION js_nth_value(arg anyelement, nth int) RETURNS anyelement AS $$ var winobj = plv8.get_window_object(); nth = winobj.get_func_arg_current(1); if (nth <= 0) plv8.elog(ERROR, "argument of nth_value must be greater than zero"); return winobj.get_func_arg_in_frame(0, nth - 1, winobj.SEEK_HEAD, false); $$ LANGUAGE plv8 WINDOW; CREATE TABLE empsalary ( depname varchar, empno bigint, salary int, enroll_date date ); INSERT INTO empsalary VALUES ('develop', 10, 5200, '2007-08-01'), ('sales', 1, 5000, '2006-10-01'), ('personnel', 5, 3500, '2007-12-10'), ('sales', 4, 4800, '2007-08-08'), ('personnel', 2, 3900, '2006-12-23'), ('develop', 7, 4200, '2008-01-01'), ('develop', 9, 4500, '2008-01-01'), ('sales', 3, 4800, '2007-08-01'), ('develop', 8, 6000, '2006-10-01'), ('develop', 11, 5200, '2007-08-15'); SELECT row_number() OVER (w), js_row_number() OVER (w) FROM empsalary WINDOW w AS (ORDER BY salary); row_number | js_row_number ------------+--------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 8 | 8 9 | 9 10 | 10 (10 rows) SELECT rank() OVER (w), js_rank() OVER (w) FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary); rank | js_rank ------+--------- 1 | 1 2 | 2 3 | 3 3 | 3 5 | 5 1 | 1 2 | 2 1 | 1 1 | 1 3 | 3 (10 rows) SELECT dense_rank() OVER (w), js_dense_rank() OVER (w) FROM empsalary WINDOW w AS (ORDER BY salary); dense_rank | js_dense_rank ------------+--------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 5 | 5 6 | 6 7 | 7 7 | 7 8 | 8 (10 rows) SELECT percent_rank() OVER (w), js_percent_rank() OVER (w) FROM empsalary WINDOW w AS (ORDER BY salary); percent_rank | js_percent_rank -------------------+------------------- 0 | 0 0.111111111111111 | 0.111111111111111 0.222222222222222 | 0.222222222222222 0.333333333333333 | 0.333333333333333 0.444444444444444 | 0.444444444444444 0.444444444444444 | 0.444444444444444 0.666666666666667 | 0.666666666666667 0.777777777777778 | 0.777777777777778 0.777777777777778 | 0.777777777777778 1 | 1 (10 rows) SELECT cume_dist() OVER (w), js_cume_dist() OVER (w) FROM empsalary WINDOW w AS (ORDER BY salary); cume_dist | js_cume_dist -----------+-------------- 0.1 | 0.1 0.2 | 0.2 0.3 | 0.3 0.4 | 0.4 0.6 | 0.6 0.6 | 0.6 0.7 | 0.7 0.9 | 0.9 0.9 | 0.9 1 | 1 (10 rows) SELECT ntile(3) OVER (w), js_ntile(3) OVER (w) FROM empsalary WINDOW w AS (ORDER BY salary); ntile | js_ntile -------+---------- 1 | 1 1 | 1 1 | 1 1 | 1 2 | 2 2 | 2 2 | 2 3 | 3 3 | 3 3 | 3 (10 rows) SELECT lag(enroll_date) OVER (w), js_lag(enroll_date) OVER (w) FROM empsalary WINDOW w AS (ORDER BY salary); lag | js_lag ------------+------------ | 12-10-2007 | 12-10-2007 12-23-2006 | 12-23-2006 01-01-2008 | 01-01-2008 01-01-2008 | 01-01-2008 08-08-2007 | 08-08-2007 08-01-2007 | 08-01-2007 10-01-2006 | 10-01-2006 08-15-2007 | 08-15-2007 08-01-2007 | 08-01-2007 (10 rows) SELECT lead(enroll_date) OVER (w), js_lead(enroll_date) OVER (w) FROM empsalary WINDOW w AS (ORDER BY salary); lead | js_lead ------------+------------ 12-23-2006 | 12-23-2006 01-01-2008 | 01-01-2008 01-01-2008 | 01-01-2008 08-08-2007 | 08-08-2007 08-01-2007 | 08-01-2007 10-01-2006 | 10-01-2006 08-15-2007 | 08-15-2007 08-01-2007 | 08-01-2007 10-01-2006 | 10-01-2006 | (10 rows) SELECT first_value(empno) OVER (w ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING), js_first_value(empno) OVER (w ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) FROM empsalary WINDOW w AS (ORDER BY salary); first_value | js_first_value -------------+---------------- 5 | 5 5 | 5 5 | 5 2 | 2 7 | 7 9 | 9 4 | 4 3 | 3 1 | 1 11 | 11 (10 rows) SELECT last_value(empno) OVER (w ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING), js_last_value(empno) OVER (w ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING) FROM empsalary WINDOW w AS (ORDER BY salary); last_value | js_last_value ------------+--------------- | 5 | 5 2 | 2 7 | 7 9 | 9 4 | 4 3 | 3 1 | 1 11 | 11 10 | 10 (10 rows) SELECT nth_value(empno, 2) OVER (w ROWS BETWEEN 1 FOLLOWING AND 3 FOLLOWING), js_nth_value(empno, 2) OVER (w ROWS BETWEEN 1 FOLLOWING AND 3 FOLLOWING) FROM empsalary WINDOW w AS (ORDER BY salary); nth_value | js_nth_value -----------+-------------- 7 | 7 9 | 9 4 | 4 3 | 3 1 | 1 11 | 11 10 | 10 8 | 8 | | (10 rows) CREATE FUNCTION bad_alloc(sz text) RETURNS void AS $$ var winobj = plv8.get_window_object(); var context = winobj.get_partition_local(sz - 0) || {}; context.long_text_key_and_value = "blablablablablablablablablablablablablablablabla"; winobj.set_partition_local(context); $$ LANGUAGE plv8 WINDOW; SELECT bad_alloc('5') OVER (); ERROR: Error: window local memory overflow DETAIL: bad_alloc() LINE 5: winobj.set_partition_local(context); SELECT bad_alloc('not a number') OVER (); ERROR: Error: window local memory overflow DETAIL: bad_alloc() LINE 5: winobj.set_partition_local(context); SELECT bad_alloc('1000') OVER (); -- not so bad bad_alloc ----------- (1 row) CREATE FUNCTION non_window() RETURNS void AS $$ var winobj = plv8.get_window_object(); $$ LANGUAGE plv8; SELECT non_window(); ERROR: Error: get_window_object called in wrong context DETAIL: non_window() LINE 2: var winobj = plv8.get_window_object(); plv8/expected/startup_pre.out0000664000401600040160000000111312704526555014722 0ustar cbecbeSET client_min_messages = ERROR; CREATE TABLE public.plv8_modules ( modname name primary key, code text not null ); insert into plv8_modules values ('testme','bar=98765;'); create function startup() returns void language plv8 as $$ foo=14378; load_module = function(modname) { var rows = plv8.execute("SELECT code from plv8_modules where modname = $1", [modname]); for (var r = 0; r < rows.length; r++) { var code = rows[r].code; eval("(function() { " + code + "})")(); plv8.elog (NOTICE, 'loaded module: ' + modname); } }; $$; plv8/expected/plv8.out0000664000401600040160000004765413064535725013266 0ustar cbecbe-- 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'); scalar_to_record ------------------ (1,a) (1 row) 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) CREATE FUNCTION return_record(i integer, t text) RETURNS record AS $$ return { "i": i, "t": t }; $$ LANGUAGE plv8; SELECT * FROM return_record(1, 'a'); ERROR: a column definition list is required for functions returning "record" LINE 1: SELECT * FROM return_record(1, 'a'); ^ SELECT * FROM return_record(1, 'a') AS t(j integer, s text); ERROR: input of anonymous composite types is not implemented SELECT * FROM return_record(1, 'a') AS t(x text, y text); ERROR: input of anonymous composite types is not implemented CREATE FUNCTION set_of_records() RETURNS SETOF rec AS $$ plv8.return_next( { "i": 1, "t": "a" } ); plv8.return_next( { "i": 2, "t": "b" } ); plv8.return_next( { "i": 3, "t": "c" } ); $$ LANGUAGE plv8; SELECT * FROM set_of_records(); i | t ---+--- 1 | a 2 | b 3 | c (3 rows) CREATE FUNCTION set_of_record_but_non_obj() RETURNS SETOF rec AS $$ plv8.return_next( "abc" ); $$ LANGUAGE plv8; SELECT * FROM set_of_record_but_non_obj(); ERROR: Error: argument must be an object DETAIL: set_of_record_but_non_obj() LINE 2: plv8.return_next( "abc" ); CREATE FUNCTION set_of_integers() RETURNS SETOF integer AS $$ plv8.return_next( 1 ); plv8.return_next( 2 ); plv8.return_next( 3 ); $$ LANGUAGE plv8; SELECT * FROM set_of_integers(); set_of_integers ----------------- 1 2 3 (3 rows) CREATE FUNCTION set_of_nest() RETURNS SETOF float AS $$ plv8.return_next( -0.2 ); var rows = plv8.execute( "SELECT set_of_integers() AS i" ); plv8.return_next( rows[0].i ); return 0.2; $$ LANGUAGE plv8; SELECT * FROM set_of_nest(); set_of_nest ------------- -0.2 1 0.2 (3 rows) CREATE FUNCTION set_of_unnamed_records() RETURNS SETOF record AS $$ return [ { i: true } ]; $$ LANGUAGE plv8; SELECT set_of_unnamed_records(); ERROR: function returning record called in context that cannot accept type record SELECT * FROM set_of_unnamed_records() t (i bool); i --- t (1 row) CREATE OR REPLACE FUNCTION set_of_unnamed_records() RETURNS SETOF record AS $$ plv8.return_next({"a": 1, "b": 2}); return; $$ LANGUAGE plv8; -- not enough fields specified SELECT * FROM set_of_unnamed_records() AS x(a int); a --- 1 (1 row) -- field names mismatch SELECT * FROM set_of_unnamed_records() AS x(a int, c int); ERROR: Error: field name / property name mismatch DETAIL: set_of_unnamed_records() LINE 2: plv8.return_next({"a": 1, "b": 2}); -- name counts and values match SELECT * FROM set_of_unnamed_records() AS x(a int, b int); a | b ---+--- 1 | 2 (1 row) -- return type check CREATE OR REPLACE FUNCTION bogus_return_type() RETURNS int[] AS $$ return 1; $$ LANGUAGE plv8; SELECT bogus_return_type(); ERROR: value is not an Array -- INOUT and OUT parameters CREATE FUNCTION one_inout(a integer, INOUT b text) AS $$ return a + b; $$ LANGUAGE plv8; SELECT one_inout(5, 'ABC'); one_inout ----------- 5ABC (1 row) CREATE FUNCTION one_out(OUT o text, i integer) AS $$ return 'ABC' + i; $$ LANGUAGE plv8; SELECT one_out(123); one_out --------- ABC123 (1 row) -- polymorphic types CREATE FUNCTION polymorphic(poly anyarray) returns anyelement AS $$ return poly[0]; $$ LANGUAGE plv8; SELECT polymorphic(ARRAY[10, 11]), polymorphic(ARRAY['foo', 'bar']); polymorphic | polymorphic -------------+------------- 10 | foo (1 row) -- typed array CREATE FUNCTION fastsum(ary plv8_int4array) RETURNS int8 AS $$ sum = 0; for (var i = 0; i < ary.length; i++) { sum += ary[i]; } return sum; $$ LANGUAGE plv8 IMMUTABLE STRICT; SELECT fastsum(ARRAY[1, 2, 3, 4, 5]); fastsum --------- 15 (1 row) SELECT fastsum(ARRAY[NULL, 2]); ERROR: NULL element, or multi-dimension array not allowed in external array type -- elog() CREATE FUNCTION test_elog(arg text) RETURNS void AS $$ plv8.elog(NOTICE, 'args =', arg); plv8.elog(WARNING, 'warning'); try{ plv8.elog(ERROR, 'ERROR'); }catch(e){ plv8.elog(INFO, e); } plv8.elog(21, 'FATAL is not allowed'); $$ LANGUAGE plv8; SELECT test_elog('ABC'); NOTICE: args = ABC WARNING: warning INFO: Error: ERROR ERROR: Error: invalid error level DETAIL: test_elog() LINE 9: plv8.elog(21, 'FATAL is not allowed'); -- execute() CREATE TABLE test_tbl (i integer, s text); CREATE FUNCTION test_sql() RETURNS integer AS $$ // for name[] conversion test, add current_schemas() var rows = plv8.execute("SELECT i, 's' || i AS s, current_schemas(true) AS c FROM generate_series(1, 4) AS t(i)"); for (var r = 0; r < rows.length; r++) { var result = plv8.execute("INSERT INTO test_tbl VALUES(" + rows[r].i + ",'" + rows[r].s + "')"); plv8.elog(NOTICE, JSON.stringify(rows[r]), result); } return rows.length; $$ LANGUAGE plv8; SELECT test_sql(); NOTICE: {"i":1,"s":"s1","c":["pg_catalog","public"]} 1 NOTICE: {"i":2,"s":"s2","c":["pg_catalog","public"]} 1 NOTICE: {"i":3,"s":"s3","c":["pg_catalog","public"]} 1 NOTICE: {"i":4,"s":"s4","c":["pg_catalog","public"]} 1 test_sql ---------- 4 (1 row) SELECT * FROM test_tbl; i | s ---+---- 1 | s1 2 | s2 3 | s3 4 | s4 (4 rows) CREATE FUNCTION return_sql() RETURNS SETOF test_tbl AS $$ return plv8.execute( "SELECT i, $1 || i AS s FROM generate_series(1, $2) AS t(i)", [ 's', 4 ] ); $$ LANGUAGE plv8; SELECT * FROM return_sql(); i | s ---+---- 1 | s1 2 | s2 3 | s3 4 | s4 (4 rows) CREATE FUNCTION test_sql_error() RETURNS void AS $$ plv8.execute("ERROR") $$ LANGUAGE plv8; SELECT test_sql_error(); ERROR: Error: syntax error at or near "ERROR" DETAIL: test_sql_error() LINE 1: plv8.execute("ERROR") CREATE FUNCTION catch_sql_error() RETURNS void AS $$ try { plv8.execute("throw SQL error"); plv8.elog(NOTICE, "should not come here"); } catch (e) { plv8.elog(NOTICE, e); } $$ LANGUAGE plv8; SELECT catch_sql_error(); NOTICE: Error: syntax error at or near "throw" catch_sql_error ----------------- (1 row) CREATE FUNCTION catch_sql_error_2() RETURNS text AS $$ try { plv8.execute("throw SQL error"); plv8.elog(NOTICE, "should not come here"); } catch (e) { plv8.elog(NOTICE, e); return plv8.execute("select 'and can execute queries again' t").shift().t; } $$ LANGUAGE plv8; SELECT catch_sql_error_2(); NOTICE: Error: syntax error at or near "throw" catch_sql_error_2 ------------------------------- and can execute queries again (1 row) -- subtransaction() CREATE TABLE subtrant(a int); CREATE FUNCTION test_subtransaction_catch() RETURNS void AS $$ try { plv8.subtransaction(function(){ plv8.execute("INSERT INTO subtrant VALUES(1)"); plv8.execute("INSERT INTO subtrant VALUES(1/0)"); }); } catch (e) { plv8.elog(NOTICE, e); plv8.execute("INSERT INTO subtrant VALUES(2)"); } $$ LANGUAGE plv8; SELECT test_subtransaction_catch(); NOTICE: Error: division by zero test_subtransaction_catch --------------------------- (1 row) SELECT * FROM subtrant; a --- 2 (1 row) TRUNCATE subtrant; CREATE FUNCTION test_subtransaction_throw() RETURNS void AS $$ plv8.subtransaction(function(){ plv8.execute("INSERT INTO subtrant VALUES(1)"); plv8.execute("INSERT INTO subtrant VALUES(1/0)"); }); $$ LANGUAGE plv8; SELECT test_subtransaction_throw(); ERROR: Error: division by zero DETAIL: test_subtransaction_throw() LINE 2: plv8.subtransaction(function(){ SELECT * FROM subtrant; a --- (0 rows) -- REPLACE FUNCTION CREATE FUNCTION replace_test() RETURNS integer AS $$ return 1; $$ LANGUAGE plv8; SELECT replace_test(); replace_test -------------- 1 (1 row) CREATE OR REPLACE FUNCTION replace_test() RETURNS integer AS $$ return 2; $$ LANGUAGE plv8; SELECT replace_test(); replace_test -------------- 2 (1 row) -- TRIGGER CREATE FUNCTION test_trigger() RETURNS trigger AS $$ plv8.elog(NOTICE, "NEW = ", JSON.stringify(NEW)); plv8.elog(NOTICE, "OLD = ", JSON.stringify(OLD)); plv8.elog(NOTICE, "TG_OP = ", TG_OP); plv8.elog(NOTICE, "TG_ARGV = ", TG_ARGV); if (TG_OP == "UPDATE") { NEW.i = 102; return NEW; } $$ 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":102,"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) -- One more trigger CREATE FUNCTION test_trigger2() RETURNS trigger AS $$ var tuple; switch (TG_OP) { case "INSERT": tuple = NEW; break; case "UPDATE": tuple = OLD; break; case "DELETE": tuple = OLD; break; default: return; } if (tuple.subject == "skip") { return null; } if (tuple.subject == "modify" && NEW) { NEW.val = tuple.val * 2; return NEW; } $$ LANGUAGE "plv8"; CREATE TABLE trig_table (subject text, val int); INSERT INTO trig_table VALUES('skip', 1); CREATE TRIGGER test_trigger2 BEFORE INSERT OR UPDATE OR DELETE ON trig_table FOR EACH ROW EXECUTE PROCEDURE test_trigger2(); INSERT INTO trig_table VALUES ('skip', 1), ('modify', 2), ('noop', 3); SELECT * FROM trig_table; subject | val ---------+----- skip | 1 modify | 4 noop | 3 (3 rows) UPDATE trig_table SET val = 10; SELECT * FROM trig_table; subject | val ---------+----- skip | 1 modify | 8 noop | 10 (3 rows) DELETE FROM trig_table; SELECT * FROM trig_table; subject | val ---------+----- skip | 1 (1 row) -- 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"); -- SPI operations CREATE FUNCTION prep1() RETURNS void AS $$ var plan = plv8.prepare("SELECT * FROM test_tbl"); plv8.elog(INFO, plan.toString()); var rows = plan.execute(); for(var i = 0; i < rows.length; i++) { plv8.elog(INFO, JSON.stringify(rows[i])); } var cursor = plan.cursor(); plv8.elog(INFO, cursor.toString()); var row; while(row = cursor.fetch()) { plv8.elog(INFO, JSON.stringify(row)); } cursor.close(); var cursor = plan.cursor(); var rows; rows = cursor.fetch(2); plv8.elog(INFO, JSON.stringify(rows)); rows = cursor.fetch(-2); plv8.elog(INFO, JSON.stringify(rows)); cursor.move(1); rows = cursor.fetch(3); plv8.elog(INFO, JSON.stringify(rows)); cursor.move(-2); rows = cursor.fetch(3); plv8.elog(INFO, JSON.stringify(rows)); cursor.close(); plan.free(); var plan = plv8.prepare("SELECT * FROM test_tbl WHERE i = $1 and s = $2", ["int", "text"]); var rows = plan.execute([2, "s2"]); plv8.elog(INFO, "rows.length = ", rows.length); var cursor = plan.cursor([2, "s2"]); plv8.elog(INFO, JSON.stringify(cursor.fetch())); cursor.close(); plan.free(); try{ var plan = plv8.prepare("SELECT * FROM test_tbl"); plan.free(); plan.execute(); }catch(e){ plv8.elog(WARNING, e); } try{ var plan = plv8.prepare("SELECT * FROM test_tbl"); var cursor = plan.cursor(); cursor.close(); cursor.fetch(); }catch(e){ plv8.elog(WARNING, e); } $$ LANGUAGE plv8 STRICT; SELECT prep1(); INFO: [object PreparedPlan] INFO: {"i":2,"s":"s2"} INFO: {"i":3,"s":"s3"} INFO: {"i":4,"s":"s4"} INFO: [object Cursor] INFO: {"i":2,"s":"s2"} INFO: {"i":3,"s":"s3"} INFO: {"i":4,"s":"s4"} INFO: [{"i":2,"s":"s2"},{"i":3,"s":"s3"}] INFO: [{"i":2,"s":"s2"}] INFO: [{"i":3,"s":"s3"},{"i":4,"s":"s4"}] INFO: [{"i":4,"s":"s4"}] INFO: rows.length = 1 INFO: {"i":2,"s":"s2"} WARNING: Error: plan expected -1 argument(s), given is 0 WARNING: Error: cannot find cursor prep1 ------- (1 row) -- find_function CREATE FUNCTION callee(a int) RETURNS int AS $$ return a * a $$ LANGUAGE plv8; CREATE FUNCTION sqlf(int) RETURNS int AS $$ SELECT $1 * $1 $$ LANGUAGE sql; CREATE FUNCTION caller(a int, t int) RETURNS int AS $$ var func; if (t == 1) { func = plv8.find_function("callee"); } else if (t == 2) { func = plv8.find_function("callee(int)"); } else if (t == 3) { func = plv8.find_function("sqlf"); } else if (t == 4) { func = plv8.find_function("callee(int, int)"); } else if (t == 5) { try{ func = plv8.find_function("caller()"); }catch(e){ func = function(a){ return a }; } } return func(a); $$ LANGUAGE plv8; SELECT caller(10, 1); caller -------- 100 (1 row) SELECT caller(10, 2); caller -------- 100 (1 row) SELECT caller(10, 3); ERROR: Error: javascript function is not found for "sqlf" DETAIL: caller() LINE 8: func = plv8.find_function("sqlf"); SELECT caller(10, 4); ERROR: Error: function "callee(int, int)" does not exist DETAIL: caller() LINE 10: func = plv8.find_function("callee(int, int)"); SELECT caller(10, 5); caller -------- 10 (1 row) -- quote_* CREATE FUNCTION plv8_quotes(s text) RETURNS text AS $$ return [plv8.quote_literal(s), plv8.quote_nullable(s), plv8.quote_ident(s)].join(":"); $$ LANGUAGE plv8 IMMUTABLE; SELECT plv8_quotes('select'); plv8_quotes ---------------------------- 'select':'select':"select" (1 row) SELECT plv8_quotes('kevin''s name'); plv8_quotes ------------------------------------------------ 'kevin''s name':'kevin''s name':"kevin's name" (1 row) SELECT plv8_quotes(NULL); plv8_quotes -------------------- 'null':NULL:"null" (1 row) DROP TABLE IF EXISTS t_attdrop CASCADE; NOTICE: table "t_attdrop" does not exist, skipping CREATE TABLE t_attdrop AS SELECT i a, i b, i c FROM generate_series(1, 10)i; CREATE OR REPLACE FUNCTION f_attdrop(tbl t_attdrop) RETURNS int AS $$ return tbl.a; $$ LANGUAGE plv8; CREATE OR REPLACE FUNCTION f_attdrop(a int) RETURNS t_attdrop AS $$ return {a: a, b: 0, c: 10}; $$ LANGUAGE plv8; ALTER TABLE t_attdrop DROP COLUMN b; SELECT f_attdrop(t.*) FROM t; ERROR: relation "t" does not exist LINE 1: SELECT f_attdrop(t.*) FROM t; ^ SELECT f_attdrop(2); f_attdrop ----------- (2,10) (1 row) create table plv8test ( id serial primary key, data json, sum integer, num integer); insert into plv8test (data, sum, num) values ('{"a": 1, "b": 2}', 0, 0); insert into plv8test (data, sum, num) values ('{"a": 3, "b": 4}', 0, 0); insert into plv8test (data, sum, num) values ('{"a": 3, "b": 4}', 0, 0); CREATE OR REPLACE FUNCTION plv8_trigger_handler() RETURNS trigger AS $$ var sum = 0; for (var k in NEW.data){ sum += NEW.data[k];} NEW.sum = sum return NEW; $$ LANGUAGE plv8; CREATE TRIGGER plv8test_trigger BEFORE INSERT OR UPDATE ON plv8test FOR EACH ROW EXECUTE PROCEDURE plv8_trigger_handler(); -- test OK update plv8test set num = 2 where id =2; -- then add two fields and drop one of them alter table plv8test add column repro1 varchar; alter table plv8test add column repro2 varchar; alter table plv8test drop column repro1; -- dropped columns should work with trigger update plv8test set repro2='test'; plv8/expected/json_conv.out0000664000401600040160000000050612704526555014355 0ustar cbecbeCREATE FUNCTION conv(o json) RETURNS json AS $$ if (o instanceof Array) { o[1] = 10; } else if (typeof(o) == 'object') { o.i = 10; } return o; $$ LANGUAGE plv8; SELECT conv('{"i": 3, "b": 20}'); conv ----------------- {"i":10,"b":20} (1 row) SELECT conv('[1, 2, 3]'); conv ---------- [1,10,3] (1 row) plv8/expected/varparam.out0000664000401600040160000000141512704526555014170 0ustar cbecbe-- parameter type deduction in 9.0+ do language plv8 $$ plv8.execute("SELECT count(*) FROM pg_class WHERE oid = $1", ["1259"]); var plan = plv8.prepare("SELECT * FROM pg_class WHERE oid = $1"); var res = plan.execute(["1259"]).shift().relname; plv8.elog(INFO, res); var cur = plan.cursor(["2610"]); var res = cur.fetch().relname; plv8.elog(INFO, res); cur.close(); plan.free(); $$; INFO: pg_class INFO: pg_index -- Show variadic argument handling do language plv8 $$ plv8.elog(INFO, JSON.stringify(plv8.execute("SELECT $1", 1))); plv8.elog(INFO, JSON.stringify(plv8.execute("SELECT $1", [1]))); plv8.elog(INFO, JSON.stringify(plv8.execute("SELECT $1 a, $2 b", 1, 2))); $$; INFO: [{"?column?":"1"}] INFO: [{"?column?":"1"}] INFO: [{"a":"1","b":"2"}] plv8/expected/dialect.out0000664000401600040160000000115012704526555013760 0ustar cbecbeCREATE EXTENSION plcoffee; DO LANGUAGE plcoffee $$ plv8.elog(INFO, "foo") $$; INFO: foo CREATE EXTENSION plls; DO LANGUAGE plls $$ plv8.elog(INFO, "foo") $$; INFO: foo CREATE FUNCTION v8func(a int) RETURNS int[] AS $$ return [a, a, a]; $$ LANGUAGE plv8; CREATE FUNCTION coffeefunc(a int) RETURNS int[] AS $$ return plv8.find_function('v8func')(a); $$ LANGUAGE plcoffee; SELECT coffeefunc(10); coffeefunc ------------ {10,10,10} (1 row) CREATE FUNCTION lsfunc(a int) RETURNS int[] AS $$ return plv8.find_function('v8func')(a); $$ LANGUAGE plls; SELECT lsfunc(11); lsfunc ------------ {11,11,11} (1 row) plv8/expected/jsonb_conv.out0000664000401600040160000000151012704526555014513 0ustar cbecbeCREATE FUNCTION convb(o jsonb) RETURNS jsonb AS $$ if (o instanceof Array) { o[1] = 10; } else if (typeof(o) == 'object') { o.i = 10; } return o; $$ LANGUAGE plv8; SELECT convb('{"i": 3, "b": 20}'); convb -------------------- {"b": 20, "i": 10} (1 row) SELECT convb('[1, 2, 3]'); convb ------------ [1, 10, 3] (1 row) CREATE FUNCTION get_keyb(key text, json_raw jsonb) RETURNS jsonb LANGUAGE plv8 IMMUTABLE STRICT AS $$ var val = json_raw[key]; var ret = {}; ret[key] = val; return ret; $$; CREATE TABLE jsonbonly ( data jsonb ); COPY jsonbonly (data) FROM stdin; -- Call twice to test the function cache. SELECT get_keyb('ok', data) FROM jsonbonly; get_keyb -------------- {"ok": true} (1 row) SELECT get_keyb('ok', data) FROM jsonbonly; get_keyb -------------- {"ok": true} (1 row) plv8/expected/startup.out0000664000401600040160000000161213073210527014045 0ustar cbecbe-- test startup failure set plv8.start_proc = foo; do $$ plv8.elog(NOTICE, 'foo = ' + foo) $$ language plv8; ERROR: function "foo" does not exist \c set plv8.start_proc = startup; do $$ plv8.elog(NOTICE, 'foo = ' + foo) $$ language plv8; NOTICE: foo = 14378 update plv8_modules set code = 'foo=98765;' where modname = 'startup'; -- startup code should not be reloaded do $$ plv8.elog(NOTICE, 'foo = ' + foo) $$ language plv8; NOTICE: foo = 14378 do $$ load_module('testme'); plv8.elog (NOTICE,'bar = ' + bar);$$ language plv8; NOTICE: loaded module: testme NOTICE: bar = 98765 CREATE ROLE someone_else; SET ROLE to someone_else; reset plv8.start_proc; -- should fail because of a reference error do $$ plv8.elog(NOTICE, 'foo = ' + foo) $$ language plv8; ERROR: ReferenceError: foo is not defined DETAIL: undefined() LINE 1: plv8.elog(NOTICE, 'foo = ' + foo) RESET ROLE; DROP ROLE someone_else; plv8/expected/init.out0000664000401600040160000000013012704526555013313 0ustar cbecbe-- INSTALL SET client_min_messages = warning; \set ECHO none RESET client_min_messages; plv8/expected/inline.out0000664000401600040160000000044213064535725013633 0ustar cbecbeDO $$ plv8.elog(NOTICE, 'this', 'is', 'inline', 'code') $$ LANGUAGE plv8; NOTICE: this is inline code DO $$ plv8.return_next(new Object());$$ LANGUAGE plv8; ERROR: Error: return_next called in context that cannot accept a set DETAIL: undefined() LINE 1: plv8.return_next(new Object()); plv8/expected/init-extension.out0000664000401600040160000000002712704526555015332 0ustar cbecbeCREATE EXTENSION plv8; plv8/expected/json.out0000664000401600040160000000144412704526555013332 0ustar cbecbeCREATE SCHEMA plv8; CREATE FUNCTION valid_json(json text) RETURNS boolean LANGUAGE plv8 IMMUTABLE STRICT AS $$ try { JSON.parse(json); return true; } catch(e) { return false; } $$; CREATE DOMAIN plv8.json AS text CONSTRAINT json_check CHECK (valid_json(VALUE)); CREATE FUNCTION get_key(key text, json_raw text) RETURNS plv8.json LANGUAGE plv8 IMMUTABLE STRICT AS $$ var val = JSON.parse(json_raw)[key]; var ret = {}; ret[key] = val; return JSON.stringify(ret); $$; CREATE TABLE jsononly ( data plv8.json ); COPY jsononly (data) FROM stdin; -- Call twice to test the function cache. SELECT get_key('ok', data) FROM jsononly; get_key ------------- {"ok":true} (1 row) SELECT get_key('ok', data) FROM jsononly; get_key ------------- {"ok":true} (1 row) plv8/plv8.cc0000664000401600040160000012232613073210527011217 0ustar cbecbe/*------------------------------------------------------------------------- * * plv8.cc : PL/v8 handler routines. * * Copyright (c) 2009-2012, the PLV8JS Development Group. *------------------------------------------------------------------------- */ #include "plv8.h" #include extern "C" { #if PG_VERSION_NUM >= 90300 #include "access/htup_details.h" #endif #include "access/xact.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/trigger.h" #include "executor/spi.h" #include "funcapi.h" #include "miscadmin.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/memutils.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" #undef delete #undef namespace #undef typeid #undef typename #undef using PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(plv8_call_handler); PG_FUNCTION_INFO_V1(plv8_call_validator); PG_FUNCTION_INFO_V1(plcoffee_call_handler); PG_FUNCTION_INFO_V1(plcoffee_call_validator); PG_FUNCTION_INFO_V1(plls_call_handler); PG_FUNCTION_INFO_V1(plls_call_validator); Datum plv8_call_handler(PG_FUNCTION_ARGS); Datum plv8_call_validator(PG_FUNCTION_ARGS); Datum plcoffee_call_handler(PG_FUNCTION_ARGS); Datum plcoffee_call_validator(PG_FUNCTION_ARGS); Datum plls_call_handler(PG_FUNCTION_ARGS); Datum plls_call_validator(PG_FUNCTION_ARGS); void _PG_init(void); #if PG_VERSION_NUM >= 90000 PG_FUNCTION_INFO_V1(plv8_inline_handler); PG_FUNCTION_INFO_V1(plcoffee_inline_handler); PG_FUNCTION_INFO_V1(plls_inline_handler); Datum plv8_inline_handler(PG_FUNCTION_ARGS); Datum plcoffee_inline_handler(PG_FUNCTION_ARGS); Datum plls_inline_handler(PG_FUNCTION_ARGS); #endif } // extern "C" using namespace v8; typedef struct plv8_proc_cache { Oid fn_oid; Persistent function; char proname[NAMEDATALEN]; char *prosrc; TransactionId fn_xmin; ItemPointerData fn_tid; Oid user_id; int nargs; bool retset; /* true if SRF */ Oid rettype; Oid argtypes[FUNC_MAX_ARGS]; } plv8_proc_cache; /* * The function and context are created at the first invocation. Their * lifetime is same as plv8_proc, but they are not palloc'ed memory, * so we need to clear them at the end of transaction. */ typedef struct plv8_exec_env { Persistent recv; Persistent context; struct plv8_exec_env *next; } plv8_exec_env; /* * We cannot cache plv8_type inter executions because it has FmgrInfo fields. * So, we cache rettype and argtype in fn_extra only during one execution. */ typedef struct plv8_proc { plv8_proc_cache *cache; plv8_exec_env *xenv; TypeFuncClass functypclass; /* For SRF */ plv8_type rettype; plv8_type argtypes[FUNC_MAX_ARGS]; } plv8_proc; /* * For the security reasons, the global context is separated * between users and it's associated with user id. */ typedef struct plv8_context { Persistent context; Oid user_id; } plv8_context; static HTAB *plv8_proc_cache_hash = NULL; static plv8_exec_env *exec_env_head = NULL; extern const unsigned char coffee_script_binary_data[]; extern const unsigned char livescript_binary_data[]; /* * lower_case_functions are postgres-like C functions. * They could raise errors with elog/ereport(ERROR). */ static plv8_proc *plv8_get_proc(Oid fn_oid, FunctionCallInfo fcinfo, bool validate, char ***argnames) throw(); static void plv8_xact_cb(XactEvent event, void *arg); /* * CamelCaseFunctions are C++ functions. * They could raise errors with C++ throw statements, or never throw exceptions. */ static plv8_exec_env *CreateExecEnv(Handle script); static plv8_proc *Compile(Oid fn_oid, FunctionCallInfo fcinfo, bool validate, bool is_trigger, Dialect dialect); static Local CompileFunction(Handle global_context, const char *proname, int proarglen, const char *proargs[], const char *prosrc, bool is_trigger, bool retset, Dialect dialect); static Datum CallFunction(PG_FUNCTION_ARGS, plv8_exec_env *xenv, int nargs, plv8_type argtypes[], plv8_type *rettype); static Datum CallSRFunction(PG_FUNCTION_ARGS, plv8_exec_env *xenv, int nargs, plv8_type argtypes[], plv8_type *rettype); static Datum CallTrigger(PG_FUNCTION_ARGS, plv8_exec_env *xenv); static Persistent GetGlobalContext(); static Persistent GetGlobalObjectTemplate(); /* A GUC to specify a custom start up function to call */ static char *plv8_start_proc = NULL; /* A GUC to specify the remote debugger port */ static int plv8_debugger_port; /* * We use vector instead of hash since the size of this array * is expected to be short in most cases. */ static std::vector ContextVector; #ifdef ENABLE_DEBUGGER_SUPPORT v8::Persistent debug_message_context; void DispatchDebugMessages() { // We are in some random thread. We should already have v8::Locker acquired // (we requested this when registered this callback). We was called // because new debug messages arrived; they may have already been processed, // but we shouldn't worry about this. // // All we have to do is to set context and call ProcessDebugMessages. // // We should decide which V8 context to use here. This is important for // "evaluate" command, because it must be executed some context. // In our sample we have only one context, so there is nothing really to // think about. v8::Context::Scope scope(debug_message_context); v8::Debug::ProcessDebugMessages(); } #endif // ENABLE_DEBUGGER_SUPPORT void _PG_init(void) { HASHCTL hash_ctl = { 0 }; hash_ctl.keysize = sizeof(Oid); hash_ctl.entrysize = sizeof(plv8_proc_cache); hash_ctl.hash = oid_hash; plv8_proc_cache_hash = hash_create("PLv8 Procedures", 32, &hash_ctl, HASH_ELEM | HASH_FUNCTION); DefineCustomStringVariable("plv8.start_proc", gettext_noop("PLV8 function to run once when PLV8 is first used."), NULL, &plv8_start_proc, NULL, PGC_USERSET, 0, #if PG_VERSION_NUM >= 90100 NULL, #endif NULL, NULL); DefineCustomIntVariable("plv8.debugger_port", gettext_noop("V8 remote debug port."), gettext_noop("The default value is 35432. " "This is effective only if PLV8 is built with ENABLE_DEBUGGER_SUPPORT."), &plv8_debugger_port, 35432, 0, 65536, PGC_USERSET, 0, #if PG_VERSION_NUM >= 90100 NULL, #endif NULL, NULL); RegisterXactCallback(plv8_xact_cb, NULL); EmitWarningsOnPlaceholders("plv8"); } static void plv8_xact_cb(XactEvent event, void *arg) { plv8_exec_env *env = exec_env_head; while (env) { if (!env->recv.IsEmpty()) { env->recv.Dispose(); env->recv.Clear(); } env = env->next; /* * Each item was allocated in TopTransactionContext, so * it will be freed eventually. */ } exec_env_head = NULL; } static inline plv8_exec_env * plv8_new_exec_env() { plv8_exec_env *xenv = (plv8_exec_env *) MemoryContextAllocZero(TopTransactionContext, sizeof(plv8_exec_env)); new(&xenv->context) Persistent(); new(&xenv->recv) Persistent(); /* * Add it to the list, which will be freed in the end of top transaction. */ xenv->next = exec_env_head; exec_env_head = xenv; return xenv; } static Datum common_pl_call_handler(PG_FUNCTION_ARGS, Dialect dialect) throw() { Oid fn_oid = fcinfo->flinfo->fn_oid; bool is_trigger = CALLED_AS_TRIGGER(fcinfo); try { #ifdef ENABLE_DEBUGGER_SUPPORT Locker lock; #endif // ENABLE_DEBUGGER_SUPPORT HandleScope handle_scope; if (!fcinfo->flinfo->fn_extra) { plv8_proc *proc = Compile(fn_oid, fcinfo, false, is_trigger, dialect); proc->xenv = CreateExecEnv(proc->cache->function); fcinfo->flinfo->fn_extra = proc; } plv8_proc *proc = (plv8_proc *) fcinfo->flinfo->fn_extra; plv8_proc_cache *cache = proc->cache; if (is_trigger) return CallTrigger(fcinfo, proc->xenv); else if (cache->retset) return CallSRFunction(fcinfo, proc->xenv, cache->nargs, proc->argtypes, &proc->rettype); else return CallFunction(fcinfo, proc->xenv, cache->nargs, proc->argtypes, &proc->rettype); } catch (js_error& e) { e.rethrow(); } catch (pg_error& e) { e.rethrow(); } return (Datum) 0; // keep compiler quiet } Datum plv8_call_handler(PG_FUNCTION_ARGS) { return common_pl_call_handler(fcinfo, PLV8_DIALECT_NONE); } Datum plcoffee_call_handler(PG_FUNCTION_ARGS) { return common_pl_call_handler(fcinfo, PLV8_DIALECT_COFFEE); } Datum plls_call_handler(PG_FUNCTION_ARGS) { return common_pl_call_handler(fcinfo, PLV8_DIALECT_LIVESCRIPT); } #if PG_VERSION_NUM >= 90000 static Datum common_pl_inline_handler(PG_FUNCTION_ARGS, Dialect dialect) throw() { InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0)); Assert(IsA(codeblock, InlineCodeBlock)); try { #ifdef ENABLE_DEBUGGER_SUPPORT Locker lock; #endif // ENABLE_DEBUGGER_SUPPORT HandleScope handle_scope; char *source_text = codeblock->source_text; Handle global_context = GetGlobalContext(); Handle function = CompileFunction(global_context, NULL, 0, NULL, source_text, false, false, dialect); plv8_exec_env *xenv = CreateExecEnv(function); return CallFunction(fcinfo, xenv, 0, NULL, NULL); } catch (js_error& e) { e.rethrow(); } catch (pg_error& e) { e.rethrow(); } return (Datum) 0; // keep compiler quiet } Datum plv8_inline_handler(PG_FUNCTION_ARGS) { return common_pl_inline_handler(fcinfo, PLV8_DIALECT_NONE); } Datum plcoffee_inline_handler(PG_FUNCTION_ARGS) { return common_pl_inline_handler(fcinfo, PLV8_DIALECT_COFFEE); } Datum plls_inline_handler(PG_FUNCTION_ARGS) { return common_pl_inline_handler(fcinfo, PLV8_DIALECT_LIVESCRIPT); } #endif /* * DoCall -- Call a JS function with SPI support. * * This function could throw C++ exceptions, but must not throw PG exceptions. */ static Local DoCall(Handle fn, Handle receiver, int nargs, Handle args[]) { TryCatch try_catch; if (SPI_connect() != SPI_OK_CONNECT) throw js_error("could not connect to SPI manager"); Local result = fn->Call(receiver, nargs, args); int status = SPI_finish(); if (result.IsEmpty()) throw js_error(try_catch); if (status < 0) throw js_error(FormatSPIStatus(status)); return result; } static Datum CallFunction(PG_FUNCTION_ARGS, plv8_exec_env *xenv, int nargs, plv8_type argtypes[], plv8_type *rettype) { Handle context = xenv->context; Context::Scope context_scope(context); Handle args[FUNC_MAX_ARGS]; Handle plv8obj; WindowFunctionSupport support(context, fcinfo); /* * In window function case, we cannot see the argument datum * in fcinfo. Instead, get them by WinGetFuncArgCurrent(). */ if (support.IsWindowCall()) { WindowObject winobj = support.GetWindowObject(); for (int i = 0; i < nargs; i++) { bool isnull; Datum arg = WinGetFuncArgCurrent(winobj, i, &isnull); args[i] = ToValue(arg, isnull, &argtypes[i]); } } else { for (int i = 0; i < nargs; i++) args[i] = ToValue(fcinfo->arg[i], fcinfo->argnull[i], &argtypes[i]); } Local fn = Local::Cast(xenv->recv->GetInternalField(0)); Local result = DoCall(fn, xenv->recv, nargs, args); if (rettype) return ToDatum(result, &fcinfo->isnull, rettype); else PG_RETURN_VOID(); } static Tuplestorestate * CreateTupleStore(PG_FUNCTION_ARGS, TupleDesc *tupdesc) { Tuplestorestate *tupstore; PG_TRY(); { ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; MemoryContext per_query_ctx; MemoryContext oldcontext; plv8_proc *proc = (plv8_proc *) fcinfo->flinfo->fn_extra; /* check to see if caller supports us returning a tuplestore */ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); if (!(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("materialize mode required, but it is not " \ "allowed in this context"))); if (!proc->functypclass) proc->functypclass = get_call_result_type(fcinfo, NULL, NULL); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); tupstore = tuplestore_begin_heap(true, false, work_mem); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; /* Build a tuple descriptor for our result type */ if (proc->rettype.typid == RECORDOID) { if (proc->functypclass != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); } if (!rsinfo->setDesc) { *tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); rsinfo->setDesc = *tupdesc; } else *tupdesc = rsinfo->setDesc; MemoryContextSwitchTo(oldcontext); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); return tupstore; } static Datum CallSRFunction(PG_FUNCTION_ARGS, plv8_exec_env *xenv, int nargs, plv8_type argtypes[], plv8_type *rettype) { plv8_proc *proc = (plv8_proc *) fcinfo->flinfo->fn_extra; TupleDesc tupdesc; Tuplestorestate *tupstore; tupstore = CreateTupleStore(fcinfo, &tupdesc); Handle context = xenv->context; Context::Scope context_scope(context); Converter conv(tupdesc, proc->functypclass == TYPEFUNC_SCALAR); Handle args[FUNC_MAX_ARGS + 1]; /* * In case this is nested via SPI, stash pre-registered converters * for the previous SRF. */ SRFSupport support(context, &conv, tupstore); for (int i = 0; i < nargs; i++) args[i] = ToValue(fcinfo->arg[i], fcinfo->argnull[i], &argtypes[i]); Local fn = Local::Cast(xenv->recv->GetInternalField(0)); Handle result = DoCall(fn, xenv->recv, nargs, args); if (result->IsUndefined()) { // no additional values } else if (result->IsArray()) { Handle array = Handle::Cast(result); // return an array of records. int length = array->Length(); for (int i = 0; i < length; i++) conv.ToDatum(array->Get(i), tupstore); } else { // return a record or a scalar conv.ToDatum(result, tupstore); } /* clean up and return the tuplestore */ tuplestore_donestoring(tupstore); return (Datum) 0; } static Datum CallTrigger(PG_FUNCTION_ARGS, plv8_exec_env *xenv) { // 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 args[10]; Datum result = (Datum) 0; Handle context = xenv->context; Context::Scope context_scope(context); if (TRIGGER_FIRED_FOR_ROW(event)) { TupleDesc tupdesc = RelationGetDescr(rel); Converter conv(tupdesc); 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"); #ifdef TRIGGER_FIRED_BY_TRUNCATE else if (TRIGGER_FIRED_BY_TRUNCATE(event)) args[5] = String::New("TRUNCATE"); #endif 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 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; TryCatch try_catch; Local fn = Local::Cast(xenv->recv->GetInternalField(0)); Handle newtup = DoCall(fn, xenv->recv, lengthof(args), args); if (newtup.IsEmpty()) throw js_error(try_catch); /* * If the function specifically returned null, return NULL to * tell executor to skip the operation. Otherwise, the function * result is the tuple to be returned. */ if (newtup->IsNull() || !TRIGGER_FIRED_FOR_ROW(event)) { result = PointerGetDatum(NULL); } else if (!newtup->IsUndefined()) { TupleDesc tupdesc = RelationGetDescr(rel); Converter conv(tupdesc); HeapTupleHeader header; header = DatumGetHeapTupleHeader(conv.ToDatum(newtup)); /* We know it's there; heap_form_tuple stores with this layout. */ result = PointerGetDatum((char *) header - HEAPTUPLESIZE); } return result; } static Datum common_pl_call_validator(PG_FUNCTION_ARGS, Dialect dialect) throw() { Oid fn_oid = PG_GETARG_OID(0); HeapTuple tuple; Form_pg_proc proc; char functyptype; bool is_trigger = false; if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, fn_oid)) PG_RETURN_VOID(); /* 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, INTERNAL, VOID or polymorphic types */ 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 && proc->prorettype != INTERNALOID && !IsPolymorphicType(proc->prorettype)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/v8 functions cannot return type %s", format_type_be(proc->prorettype)))); } ReleaseSysCache(tuple); try { #ifdef ENABLE_DEBUGGER_SUPPORT Locker lock; #endif // ENABLE_DEBUGGER_SUPPORT /* Don't use validator's fcinfo */ plv8_proc *proc = Compile(fn_oid, NULL, true, is_trigger, dialect); (void) CreateExecEnv(proc->cache->function); /* 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 } Datum plv8_call_validator(PG_FUNCTION_ARGS) { return common_pl_call_validator(fcinfo, PLV8_DIALECT_NONE); } Datum plcoffee_call_validator(PG_FUNCTION_ARGS) { return common_pl_call_validator(fcinfo, PLV8_DIALECT_COFFEE); } Datum plls_call_validator(PG_FUNCTION_ARGS) { return common_pl_call_validator(fcinfo, PLV8_DIALECT_LIVESCRIPT); } static plv8_proc * plv8_get_proc(Oid fn_oid, FunctionCallInfo fcinfo, bool validate, char ***argnames) throw() { HeapTuple procTup; plv8_proc_cache *cache; bool found; bool isnull; Datum prosrc; Oid *argtypes; char *argmodes; MemoryContext oldcontext; procTup = SearchSysCache(PROCOID, ObjectIdGetDatum(fn_oid), 0, 0, 0); if (!HeapTupleIsValid(procTup)) elog(ERROR, "cache lookup failed for function %u", fn_oid); cache = (plv8_proc_cache *) hash_search(plv8_proc_cache_hash,&fn_oid, HASH_ENTER, &found); if (found) { bool uptodate; /* * We need to check user id and dispose it if it's different from * the previous cache user id, as the V8 function is associated * with the context where it was generated. In most cases, * we can expect this doesn't affect runtime performance. */ uptodate = (!cache->function.IsEmpty() && cache->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) && ItemPointerEquals(&cache->fn_tid, &procTup->t_self) && cache->user_id == GetUserId()); if (!uptodate) { if (cache->prosrc) { pfree(cache->prosrc); cache->prosrc = NULL; } cache->function.Dispose(); cache->function.Clear(); } else { ReleaseSysCache(procTup); } } else { new(&cache->function) Persistent(); cache->prosrc = NULL; } if (cache->function.IsEmpty()) { Form_pg_proc procStruct; procStruct = (Form_pg_proc) GETSTRUCT(procTup); prosrc = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); cache->retset = procStruct->proretset; cache->rettype = procStruct->prorettype; strlcpy(cache->proname, NameStr(procStruct->proname), NAMEDATALEN); cache->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); cache->fn_tid = procTup->t_self; cache->user_id = GetUserId(); int nargs = get_func_arg_info(procTup, &argtypes, argnames, &argmodes); if (validate) { /* * Disallow non-polymorphic pseudotypes in arguments * (either IN or OUT). Internal type is used to declare * js functions for find_function(). */ for (int i = 0; i < nargs; i++) { if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO && argtypes[i] != INTERNALOID && !IsPolymorphicType(argtypes[i])) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/v8 functions cannot accept type %s", format_type_be(argtypes[i])))); } } oldcontext = MemoryContextSwitchTo(TopMemoryContext); cache->prosrc = TextDatumGetCString(prosrc); MemoryContextSwitchTo(oldcontext); ReleaseSysCache(procTup); int inargs = 0; for (int i = 0; i < nargs; i++) { Oid argtype = argtypes[i]; char argmode = argmodes ? argmodes[i] : PROARGMODE_IN; switch (argmode) { case PROARGMODE_IN: case PROARGMODE_INOUT: case PROARGMODE_VARIADIC: break; default: continue; } if (*argnames) (*argnames)[inargs] = (*argnames)[i]; cache->argtypes[inargs] = argtype; inargs++; } cache->nargs = inargs; } MemoryContext mcxt = CurrentMemoryContext; if (fcinfo) mcxt = fcinfo->flinfo->fn_mcxt; plv8_proc *proc = (plv8_proc *) MemoryContextAllocZero(mcxt, offsetof(plv8_proc, argtypes) + sizeof(plv8_type) * cache->nargs); proc->cache = cache; for (int i = 0; i < cache->nargs; i++) { Oid argtype = cache->argtypes[i]; /* Resolve polymorphic types, if this is an actual call context. */ if (fcinfo && IsPolymorphicType(argtype)) argtype = get_fn_expr_argtype(fcinfo->flinfo, i); plv8_fill_type(&proc->argtypes[i], argtype, mcxt); } Oid rettype = cache->rettype; /* Resolve polymorphic return type if this is an actual call context. */ if (fcinfo && IsPolymorphicType(rettype)) rettype = get_fn_expr_rettype(fcinfo->flinfo); plv8_fill_type(&proc->rettype, rettype, mcxt); return proc; } static plv8_exec_env * CreateExecEnv(Handle function) { plv8_exec_env *xenv; HandleScope handle_scope; PG_TRY(); { xenv = plv8_new_exec_env(); } PG_CATCH(); { throw pg_error(); } PG_END_TRY(); xenv->context = GetGlobalContext(); Context::Scope scope(xenv->context); static Persistent recv_templ; if (recv_templ.IsEmpty()) { recv_templ = Persistent::New(ObjectTemplate::New()); recv_templ->SetInternalFieldCount(1); } xenv->recv = Persistent::New(recv_templ->NewInstance()); xenv->recv->SetInternalField(0, function); return xenv; } /* Source transformation from a dialect (coffee or ls) to js */ static char * CompileDialect(const char *src, Dialect dialect) { HandleScope handle_scope; static Persistent context = Context::New((ExtensionConfiguration*)NULL); Context::Scope context_scope(context); TryCatch try_catch; Local key; char *cresult; const char *dialect_binary_data; switch (dialect) { case PLV8_DIALECT_COFFEE: if (coffee_script_binary_data[0] == '\0') throw js_error("CoffeeScript is not enabled"); key = String::NewSymbol("CoffeeScript"); dialect_binary_data = (const char *) coffee_script_binary_data; break; case PLV8_DIALECT_LIVESCRIPT: if (livescript_binary_data[0] == '\0') throw js_error("LiveScript is not enabled"); key = String::NewSymbol("LiveScript"); dialect_binary_data = (const char *) livescript_binary_data; break; default: throw js_error("Unknown Dialect"); } if (context->Global()->Get(key)->IsUndefined()) { HandleScope handle_scope; Local