Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Let parser deduce parameter types in SPI queries.

The mechanism is based on the variable parameter hook available since 9.0.
For 8.4, we still have to specify the parameter types.  plan.execute,
plan.cursor is also updated to make use of this.

Change ValueGetDatum and InferredDatumType to lower_case functions, as
they look safe and natural to use outside of C++ try block.  plv8_param.cc
is also a bit modified to reduce code complexity in plv8_func.cc.  In 8.4,
plv8_param_state is never used, but leaving it in the source makes more
readable than hiding the definition completely.
  • Loading branch information...
commit 9904bc94cb9450d7e746d1f76a1ded96fa060145 1 parent 08eebbf
@umitanuki umitanuki authored
View
6 Makefile
@@ -6,14 +6,14 @@ CUSTOM_CC = g++
JSS = coffee-script.js
# .cc created from .js
JSCS = $(JSS:.js=.cc)
-SRCS = plv8.cc plv8_type.cc plv8_func.cc $(JSCS)
+SRCS = plv8.cc plv8_type.cc plv8_func.cc plv8_param.cc $(JSCS)
OBJS = $(SRCS:.cc=.o)
MODULE_big = plv8
EXTENSION = plv8
EXTVER = 1.1.0
DATA = plv8.control plv8--$(EXTVER).sql
DATA_built = plv8.sql
-REGRESS = init-extension plv8 inline json startup_pre startup
+REGRESS = init-extension plv8 inline json startup_pre startup varparam
SHLIB_LINK := $(SHLIB_LINK) -lv8
META_VER := $(shell v8 -e 'print(JSON.parse(read("META.json")).version)')
@@ -66,7 +66,7 @@ else # 9.1
ifeq ($(shell test $(PG_VERSION_NUM) -ge 90000 && echo yes), yes)
REGRESS := init $(filter-out init-extension, $(REGRESS))
else # 9.0
-REGRESS := init $(filter-out init-extension inline startup, $(REGRESS))
+REGRESS := init $(filter-out init-extension inline startup varparam, $(REGRESS))
endif
DATA = uninstall_plv8.sql
plv8.sql.in: plv8.sql.c
View
14 expected/varparam.out
@@ -0,0 +1,14 @@
+-- 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
View
2  plv8.h
@@ -119,7 +119,7 @@ extern v8::Handle<v8::Value> ThrowError(const char *message) throw();
// plv8_type.cc
extern void plv8_fill_type(plv8_type *type, Oid typid, MemoryContext mcxt = NULL);
-extern Oid InferredDatumType(v8::Handle<v8::Value> value);
+extern Oid inferred_datum_type(v8::Handle<v8::Value> value);
extern Datum ToDatum(v8::Handle<v8::Value> value, bool *isnull, plv8_type *type);
extern v8::Local<v8::Value> ToValue(Datum datum, bool isnull, plv8_type *type);
extern v8::Local<v8::String> ToString(Datum value, plv8_type *type);
View
195 plv8_func.cc
@@ -2,6 +2,7 @@
* plv8_func.cc : PL/v8 built-in functions.
*/
#include "plv8.h"
+#include "plv8_param.h"
#include <sstream>
extern "C" {
@@ -56,7 +57,6 @@ quote_literal_cstr(const char *rawstr)
}
#endif
-
static inline Local<v8::Value>
WrapCallback(InvocationCallback func)
{
@@ -280,7 +280,7 @@ plv8_Elog(const Arguments& args)
}
static Datum
-ValueGetDatum(Handle<v8::Value> value, Oid typid, char *isnull)
+value_get_datum(Handle<v8::Value> value, Oid typid, char *isnull)
{
if (value->IsUndefined() || value->IsNull())
{
@@ -294,12 +294,71 @@ ValueGetDatum(Handle<v8::Value> value, Oid typid, char *isnull)
Datum datum;
plv8_fill_type(&typinfo, typid);
- datum = ToDatum(value, &IsNull, &typinfo);
+ try
+ {
+ datum = ToDatum(value, &IsNull, &typinfo);
+ }
+ catch (js_error& e){ e.rethrow(); }
*isnull = (IsNull ? 'n' : ' ');
return datum;
}
}
+static int
+plv8_execute_params(const char *sql, Handle<Array> 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<v8::Value> 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<v8::Value> 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;
+}
+
/*
* plv8.execute(statement, [param, ...])
*/
@@ -318,34 +377,16 @@ plv8_Execute(const Arguments &args)
params = Handle<Array>::Cast(args[1]);
int nparam = params.IsEmpty() ? 0 : params->Length();
- Datum *values = (Datum *) palloc(sizeof(Datum) * nparam);
- char *nulls = (char *) palloc(sizeof(char) * nparam);
- Oid *types = (Oid *) palloc(sizeof(Oid) * nparam);
- for (int i = 0; i < nparam; i++)
- {
- Handle<v8::Value> param = params->Get(i);
-
- types[i] = InferredDatumType(param);
- if (types[i] == InvalidOid)
- {
- char msg[1024];
-
- snprintf(msg, 1024, "parameter[%d] cannot translate to a database type", i);
- throw js_error(msg);
- }
- values[i] = ValueGetDatum(param, types[i], &nulls[i]);
- }
SubTranBlock subtran;
PG_TRY();
{
subtran.enter();
- if (nparam > 0)
- status = SPI_execute_with_args(sql, nparam,
- types, values, nulls, false, 0);
- else
+ if (nparam == 0)
status = SPI_exec(sql, 0);
+ else
+ status = plv8_execute_params(sql, params);
}
PG_CATCH();
{
@@ -370,6 +411,7 @@ plv8_Prepare(const Arguments &args)
Handle<Array> array;
int arraylen = 0;
Oid *types = NULL;
+ plv8_param_state *parstate = NULL;
if (args.Length() > 1)
{
@@ -388,7 +430,18 @@ plv8_Prepare(const Arguments &args)
PG_TRY();
{
- initial = SPI_prepare(sql, arraylen, types);
+#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);
}
@@ -403,7 +456,7 @@ plv8_Prepare(const Arguments &args)
Local<FunctionTemplate> base = FunctionTemplate::New();
base->SetClassName(String::NewSymbol("PreparedPlan"));
Local<ObjectTemplate> templ = base->InstanceTemplate();
- templ->SetInternalFieldCount(1);
+ templ->SetInternalFieldCount(2);
SetCallback(templ, "cursor", plv8_PlanCursor);
SetCallback(templ, "execute", plv8_PlanExecute);
SetCallback(templ, "free", plv8_PlanFree);
@@ -412,6 +465,10 @@ plv8_Prepare(const Arguments &args)
Local<v8::Object> result = PlanTemplate->NewInstance();
result->SetInternalField(0, External::Wrap(saved));
+#if PG_VERSION_NUM >= 90000
+ if (parstate)
+ result->SetInternalField(1, External::Wrap(parstate));
+#endif
return result;
}
@@ -429,8 +486,9 @@ plv8_PlanCursor(const Arguments &args)
int nparam = 0, argcount;
Handle<Array> params;
Portal cursor;
+ plv8_param_state *parstate = NULL;
- plan = (SPIPlanPtr) External::Unwrap(self->GetInternalField(0));
+ plan = static_cast<SPIPlanPtr>(External::Unwrap(self->GetInternalField(0)));
/* XXX: Add plan validation */
if (args.Length() > 0 && args[0]->IsArray())
@@ -439,7 +497,16 @@ plv8_PlanCursor(const Arguments &args)
nparam = params->Length();
}
- argcount = SPI_getargcount(plan);
+ /*
+ * If the plan has the variable param info, use it.
+ */
+ parstate = static_cast<plv8_param_state *>(
+ External::Unwrap(self->GetInternalField(1)));
+
+ if (parstate)
+ argcount = parstate->numParams;
+ else
+ argcount = SPI_getargcount(plan);
if (argcount != nparam)
{
@@ -460,11 +527,36 @@ plv8_PlanCursor(const Arguments &args)
for (int i = 0; i < nparam; i++)
{
Handle<v8::Value> param = params->Get(i);
- Oid typid = SPI_getargtypeid(plan, i);
+ Oid typid;
- values[i] = ValueGetDatum(param, typid, &nulls[i]);
+ if (parstate)
+ typid = parstate->paramTypes[i];
+ else
+ typid = SPI_getargtypeid(plan, i);
+
+ values[i] = value_get_datum(param, typid, &nulls[i]);
}
- cursor = SPI_cursor_open(NULL, plan, values, nulls, false);
+
+ 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<String> cname = ToString(cursor->name, strlen(cursor->name));
/*
@@ -501,6 +593,7 @@ plv8_PlanExecute(const Arguments &args)
Handle<Array> params;
SubTranBlock subtran;
int status;
+ plv8_param_state *parstate = NULL;
plan = static_cast<SPIPlanPtr>(External::Unwrap(self->GetInternalField(0)));
/* XXX: Add plan validation */
@@ -511,7 +604,16 @@ plv8_PlanExecute(const Arguments &args)
nparam = params->Length();
}
- argcount = SPI_getargcount(plan);
+ /*
+ * If the plan has the variable param info, use it.
+ */
+ parstate = static_cast<plv8_param_state *> (
+ External::Unwrap(self->GetInternalField(1)));
+
+ if (parstate)
+ argcount = parstate->numParams;
+ else
+ argcount = SPI_getargcount(plan);
if (argcount != nparam)
{
@@ -532,15 +634,30 @@ plv8_PlanExecute(const Arguments &args)
for (int i = 0; i < nparam; i++)
{
Handle<v8::Value> param = params->Get(i);
- Oid typid = SPI_getargtypeid(plan, i);
+ Oid typid;
+
+ if (parstate)
+ typid = parstate->paramTypes[i];
+ else
+ typid = SPI_getargtypeid(plan, i);
- values[i] = ValueGetDatum(param, typid, &nulls[i]);
+ values[i] = value_get_datum(param, typid, &nulls[i]);
}
PG_TRY();
{
subtran.enter();
- status = SPI_execute_plan(plan, values, nulls, false, 0);
+#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();
{
@@ -562,6 +679,7 @@ plv8_PlanFree(const Arguments &args)
{
Handle<v8::Object> self = args.This();
SPIPlanPtr plan;
+ plv8_param_state *parstate;
int status = 0;
plan = static_cast<SPIPlanPtr>(External::Unwrap(self->GetInternalField(0)));
@@ -571,6 +689,13 @@ plv8_PlanFree(const Arguments &args)
self->SetInternalField(0, External::Wrap(0));
+ parstate = static_cast<plv8_param_state *> (
+ External::Unwrap(self->GetInternalField(1)));
+
+ if (parstate)
+ pfree(parstate);
+ self->SetInternalField(1, External::Wrap(0));
+
return Int32::New(status);
}
View
195 plv8_param.cc
@@ -0,0 +1,195 @@
+/*
+ * plv8_param.cc : PL/v8 parameter handling.
+ */
+#include "plv8_param.h"
+#include <limits.h>
+
+/*
+ * Variable SPI parameter is since 9.0. Avoid include files in prior versions,
+ * as they contain C++ keywords.
+ */
+#if PG_VERSION_NUM >= 90000
+
+extern "C" {
+
+#include "catalog/pg_type.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+
+} // extern "C"
+
+
+/*
+ * In the varparams case, the caller-supplied OID array (if any) can be
+ * re-palloc'd larger at need. A zero array entry means that parameter number
+ * hasn't been seen, while UNKNOWNOID means the parameter has been used but
+ * its type is not yet known.
+ */
+
+static Node *plv8_variable_paramref_hook(ParseState *pstate, ParamRef *pref);
+static Node *plv8_variable_coerce_param_hook(ParseState *pstate, Param *param,
+ Oid targetTypeId, int32 targetTypeMod,
+ int location);
+
+void
+plv8_variable_param_setup(ParseState *pstate, void *arg)
+{
+ plv8_param_state *parstate = (plv8_param_state *) arg;
+
+ pstate->p_ref_hook_state = (void *) parstate;
+ pstate->p_paramref_hook = plv8_variable_paramref_hook;
+ pstate->p_coerce_param_hook = plv8_variable_coerce_param_hook;
+}
+
+static Node *
+plv8_variable_paramref_hook(ParseState *pstate, ParamRef *pref)
+{
+ plv8_param_state *parstate = (plv8_param_state *) pstate->p_ref_hook_state;
+ int paramno = pref->number;
+ Oid *pptype;
+ Param *param;
+
+ /* Check parameter number is in range */
+ if (paramno <= 0 || paramno > INT_MAX / sizeof(Oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_PARAMETER),
+ errmsg("there is no parameter $%d", paramno),
+ parser_errposition(pstate, pref->location)));
+ if (paramno > parstate->numParams)
+ {
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(parstate->memcontext);
+ /* Need to enlarge param array */
+ if (parstate->paramTypes)
+ parstate->paramTypes = (Oid *) repalloc(parstate->paramTypes,
+ paramno * sizeof(Oid));
+ else
+ parstate->paramTypes = (Oid *) palloc(paramno * sizeof(Oid));
+ /* Zero out the previously-unreferenced slots */
+ MemSet(parstate->paramTypes + parstate->numParams,
+ 0,
+ (paramno - parstate->numParams) * sizeof(Oid));
+ parstate->numParams = paramno;
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /* Locate param's slot in array */
+ pptype = &(parstate->paramTypes)[paramno - 1];
+
+ /* If not seen before, initialize to UNKNOWN type */
+ if (*pptype == InvalidOid)
+ *pptype = UNKNOWNOID;
+
+ param = makeNode(Param);
+ param->paramkind = PARAM_EXTERN;
+ param->paramid = paramno;
+ param->paramtype = *pptype;
+ param->paramtypmod = -1;
+#if PG_VERSION_NUM >= 90100
+ param->paramcollid = get_typcollation(param->paramtype);
+#endif
+ param->location = pref->location;
+
+ return (Node *) param;
+}
+
+static Node *
+plv8_variable_coerce_param_hook(ParseState *pstate, Param *param,
+ Oid targetTypeId, int32 targetTypeMod,
+ int location)
+{
+ if (param->paramkind == PARAM_EXTERN && param->paramtype == UNKNOWNOID)
+ {
+ /*
+ * Input is a Param of previously undetermined type, and we want to
+ * update our knowledge of the Param's type.
+ */
+ plv8_param_state *parstate =
+ (plv8_param_state *) pstate->p_ref_hook_state;
+ Oid *paramTypes = parstate->paramTypes;
+ int paramno = param->paramid;
+
+ if (paramno <= 0 || /* shouldn't happen, but... */
+ paramno > parstate->numParams)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_PARAMETER),
+ errmsg("there is no parameter $%d", paramno),
+ parser_errposition(pstate, param->location)));
+
+ if (paramTypes[paramno - 1] == UNKNOWNOID)
+ {
+ /* We've successfully resolved the type */
+ paramTypes[paramno - 1] = targetTypeId;
+ }
+ else if (paramTypes[paramno - 1] == targetTypeId)
+ {
+ /* We previously resolved the type, and it matches */
+ }
+ else
+ {
+ /* Ooops */
+ ereport(ERROR,
+ (errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+ errmsg("inconsistent types deduced for parameter $%d",
+ paramno),
+ errdetail("%s versus %s",
+ format_type_be(paramTypes[paramno - 1]),
+ format_type_be(targetTypeId)),
+ parser_errposition(pstate, param->location)));
+ }
+
+ param->paramtype = targetTypeId;
+
+ /*
+ * Note: it is tempting here to set the Param's paramtypmod to
+ * targetTypeMod, but that is probably unwise because we have no
+ * infrastructure that enforces that the value delivered for a Param
+ * will match any particular typmod. Leaving it -1 ensures that a
+ * run-time length check/coercion will occur if needed.
+ */
+ param->paramtypmod = -1;
+
+#if PG_VERSION_NUM >= 90100
+ /*
+ * This module always sets a Param's collation to be the default for
+ * its datatype. If that's not what you want, you should be using the
+ * more general parser substitution hooks.
+ */
+ param->paramcollid = get_typcollation(param->paramtype);
+#endif
+
+ /* Use the leftmost of the param's and coercion's locations */
+ if (location >= 0 &&
+ (param->location < 0 || location < param->location))
+ param->location = location;
+
+ return (Node *) param;
+ }
+
+ /* Else signal to proceed with normal coercion */
+ return NULL;
+}
+
+ParamListInfo
+plv8_setup_variable_paramlist(plv8_param_state *parstate,
+ Datum *values, char *nulls)
+{
+ ParamListInfo paramLI;
+
+ paramLI = (ParamListInfo) palloc0(sizeof(ParamListInfoData) +
+ sizeof(ParamExternData) * (parstate->numParams - 1));
+ paramLI->numParams = parstate->numParams;
+ for(int i = 0; i < parstate->numParams; i++)
+ {
+ ParamExternData *param = &paramLI->params[i];
+
+ param->value = values[i];
+ param->isnull = nulls[i] == 'n';
+ param->pflags = PARAM_FLAG_CONST;
+ param->ptype = parstate->paramTypes[i];
+ }
+
+ return paramLI;
+}
+#endif // PG_VERSION_NUM >= 90000
View
37 plv8_param.h
@@ -0,0 +1,37 @@
+#ifndef _PLV8_PARAM_H_
+#define _PLV8_PARAM_H_
+
+extern "C" {
+#include "postgres.h"
+
+/*
+ * Variable SPI parameter is since 9.0. Avoid include files in prior versions,
+ * as they contain C++ keywords.
+ */
+#include "nodes/params.h"
+#if PG_VERSION_NUM >= 90000
+#include "parser/parse_node.h"
+#endif // PG_VERSION_NUM >= 90000
+
+} // extern "C"
+
+/*
+ * In variable paramter case for SPI, the type information is filled by
+ * the parser in paramTypes and numParams. MemoryContext should be given
+ * by the caller to allocate the paramTypes in the right context.
+ */
+typedef struct plv8_param_state
+{
+ Oid *paramTypes; /* array of parameter type OIDs */
+ int numParams; /* number of array entries */
+ MemoryContext memcontext;
+} plv8_param_state;
+
+#if PG_VERSION_NUM >= 90000
+// plv8_param.cc
+extern void plv8_variable_param_setup(ParseState *pstate, void *arg);
+extern ParamListInfo plv8_setup_variable_paramlist(plv8_param_state *parstate,
+ Datum *values, char *nulls);
+#endif // PG_VERSION_NUM >= 90000
+
+#endif // _PLV8_PARAM_H_
View
7 plv8_type.cc
@@ -66,8 +66,13 @@ plv8_fill_type(plv8_type *type, Oid typid, MemoryContext mcxt)
}
}
+/*
+ * Return the database type inferred by the JS value type.
+ * If none looks appropriate, InvalidOid is returned (currently,
+ * objects and arrays are in this case).
+ */
Oid
-InferredDatumType(Handle<v8::Value> value)
+inferred_datum_type(Handle<v8::Value> value)
{
if (value->IsUndefined() || value->IsNull())
return TEXTOID;
View
12 sql/varparam.sql
@@ -0,0 +1,12 @@
+-- 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();
+$$;
Please sign in to comment.
Something went wrong with that request. Please try again.