static PyObject * PLy_cursor_query(const char *query) { PLyCursorObject *cursor; volatile MemoryContext oldcontext; volatile ResourceOwner oldowner; if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL) return NULL; cursor->portalname = NULL; cursor->closed = false; cursor->mcxt = AllocSetContextCreate(TopMemoryContext, "PL/Python cursor context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); PLy_typeinfo_init(&cursor->result, cursor->mcxt); oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; PLy_spi_subtransaction_begin(oldcontext, oldowner); PG_TRY(); { PLyExecutionContext *exec_ctx = PLy_current_execution_context(); SPIPlanPtr plan; Portal portal; pg_verifymbstr(query, strlen(query), false); plan = SPI_prepare(query, 0, NULL); if (plan == NULL) elog(ERROR, "SPI_prepare failed: %s", SPI_result_code_string(SPI_result)); portal = SPI_cursor_open(NULL, plan, NULL, NULL, exec_ctx->curr_proc->fn_readonly); SPI_freeplan(plan); if (portal == NULL) elog(ERROR, "SPI_cursor_open() failed: %s", SPI_result_code_string(SPI_result)); cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name); PLy_spi_subtransaction_commit(oldcontext, oldowner); } PG_CATCH(); { PLy_spi_subtransaction_abort(oldcontext, oldowner); return NULL; } PG_END_TRY(); Assert(cursor->portalname != NULL); return (PyObject *) cursor; }
Datum plpgsql_inline_handler(PG_FUNCTION_ARGS) { InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0)); PLpgSQL_function *func; FunctionCallInfoData fake_fcinfo; FmgrInfo flinfo; Datum retval; int rc; Assert(IsA(codeblock, InlineCodeBlock)); /* * Connect to SPI manager */ if ((rc = SPI_connect()) != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc)); /* Compile the anonymous code block */ func = plpgsql_compile_inline(fcinfo, codeblock->source_text); /* Mark the function as busy, just pro forma */ func->use_count++; /* * Set up a fake fcinfo with just enough info to satisfy * plpgsql_exec_function(). In particular note that this sets things up * with no arguments passed. */ MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo)); MemSet(&flinfo, 0, sizeof(flinfo)); fake_fcinfo.flinfo = &flinfo; flinfo.fn_oid = InvalidOid; flinfo.fn_mcxt = CurrentMemoryContext; retval = plpgsql_exec_function(func, &fake_fcinfo); /* Function should now have no remaining use-counts ... */ func->use_count--; Assert(func->use_count == 0); /* ... so we can free subsidiary storage */ plpgsql_free_function_memory(func); /* * Disconnect from SPI manager */ if ((rc = SPI_finish()) != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc)); return retval; }
Datum plpgsql_call_handler(PG_FUNCTION_ARGS) { PLpgSQL_function *func; Datum retval; int rc; /* * Connect to SPI manager */ if ((rc = SPI_connect()) != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc)); /* Find or compile the function */ func = plpgsql_compile(fcinfo, false); /* Mark the function as busy, so it can't be deleted from under us */ func->use_count++; PG_TRY(); { /* * Determine if called as function or trigger and call appropriate * subhandler */ if (CALLED_AS_TRIGGER(fcinfo)) retval = PointerGetDatum(plpgsql_exec_trigger(func, (TriggerData *) fcinfo->context)); else retval = plpgsql_exec_function(func, fcinfo); } PG_CATCH(); { /* Decrement use-count and propagate error */ func->use_count--; PG_RE_THROW(); } PG_END_TRY(); func->use_count--; /* * Disconnect from SPI manager */ if ((rc = SPI_finish()) != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc)); return retval; }
Oid getIntOid() { Oid out; int ret; bool isnull; bool do_pop = false; do_pop = _SPI_conn(); /* * Get OID of our internal data type. This is necessary because record_in and * record_out need it. */ if ( (ret = SPI_execute("SELECT 'variant._variant'::regtype::oid", true, 1)) != SPI_OK_SELECT ) elog( ERROR, "SPI_execute returned %s", SPI_result_code_string(ret)); /* Don't need to copy the tuple because Oid is pass by value */ out = DatumGetObjectId( heap_getattr(SPI_tuptable->vals[0], 1, SPI_tuptable->tupdesc, &isnull) ); /* Remember this frees everything palloc'd since our connect/push call */ _SPI_disc(do_pop); return out; }
static HV * plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int status) { HV *result; result = newHV(); hv_store(result, "status", strlen("status"), newSVpv((char *) SPI_result_code_string(status), 0), 0); hv_store(result, "processed", strlen("processed"), newSViv(processed), 0); if (status == SPI_OK_SELECT) { AV *rows; SV *row; int i; rows = newAV(); for (i = 0; i < processed; i++) { row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc); av_push(rows, row); } hv_store(result, "rows", strlen("rows"), newRV_noinc((SV *) rows), 0); } SPI_freetuptable(tuptable); return result; }
Datum variant_cast_out(PG_FUNCTION_ARGS) { Oid targettypid = get_fn_expr_rettype(fcinfo->flinfo); VariantInt vi; Datum out; if( PG_ARGISNULL(0) ) PG_RETURN_NULL(); /* No reason to format type name, so use IOFunc_input instead of IOFunc_output */ vi = make_variant_int(PG_GETARG_VARIANT(0), fcinfo, IOFunc_input); /* If original was NULL then we MUST return NULL */ if( vi->isnull ) PG_RETURN_NULL(); /* If our types match exactly we don't need to cast */ if( vi->typid == targettypid ) PG_RETURN_DATUM(vi->data); /* Keep cruft localized to just here */ { bool do_pop; int ret; bool isnull; MemoryContext cctx = CurrentMemoryContext; HeapTuple tup; StringInfoData cmdd; StringInfo cmd = &cmdd; char *nulls = " "; do_pop = _SPI_conn(); initStringInfo(cmd); appendStringInfo( cmd, "SELECT $1::%s", format_type_be(targettypid) ); /* command, nargs, Oid *argument_types, *values, *nulls, read_only, count */ if( (ret = SPI_execute_with_args( cmd->data, 1, &vi->typid, &vi->data, nulls, true, 0 )) != SPI_OK_SELECT ) elog( ERROR, "SPI_execute_with_args returned %s", SPI_result_code_string(ret)); /* * Make a copy of result tuple in previous memory context. Copying the * entire tuple is wasteful; it would be better to only copy the actual * attribute; but in this case the difference isn't very large. */ MemoryContextSwitchTo(cctx); tup = heap_copytuple(SPI_tuptable->vals[0]); out = heap_getattr(tup, 1, SPI_tuptable->tupdesc, &isnull); // getTypeOutputInfo(typoid, &foutoid, &typisvarlena); /* Remember this frees everything palloc'd since our connect/push call */ _SPI_disc(do_pop); } /* End cruft */ PG_RETURN_DATUM(out); }
static void _SPI_disc(bool pop) { int ret; if( (ret = SPI_finish()) != SPI_OK_FINISH ) elog( ERROR, "SPI_finish returned %s", SPI_result_code_string(ret)); if(pop) SPI_pop(); }
void Invocation_assertConnect(void) { int rslt; if(!currentInvocation->hasConnected) { rslt = SPI_connect(); if ( SPI_OK_CONNECT != rslt ) elog(ERROR, "SPI_connect returned %s", SPI_result_code_string(rslt)); #if PG_VERSION_NUM >= 100000 if ( NULL != currentInvocation->triggerData ) { rslt = SPI_register_trigger_data(currentInvocation->triggerData); if ( SPI_OK_TD_REGISTER != rslt ) elog(WARNING, "SPI_register_trigger_data returned %s", SPI_result_code_string(rslt)); } #endif currentInvocation->hasConnected = true; } }
static bool _SPI_conn() { int ret; if( SPI_connect() == SPI_OK_CONNECT ) return false; SPI_push(); if( (ret = SPI_connect()) != SPI_OK_CONNECT ) elog( ERROR, "SPI_connect returned %s", SPI_result_code_string(ret)); return true; }
static int get_nnode(PlxFn *plx_fn, FunctionCallInfo fcinfo) { PlxQuery *plx_q = plx_fn->hash_query; int err; SPIPlanPtr plan; Oid types[FUNC_MAX_ARGS]; Datum values[FUNC_MAX_ARGS]; char arg_nulls[FUNC_MAX_ARGS]; Datum val; bool isnull; int i; if ((err = SPI_connect()) != SPI_OK_CONNECT) plx_error(plx_fn, "SPI_connect: %s", SPI_result_code_string(err)); for (i = 0; i < plx_q->nargs; i++) { int idx = plx_q->plx_fn_arg_indexes[i]; types[i] = plx_fn->arg_types[idx]->oid; values[i] = PG_GETARG_DATUM(idx); } plan = SPI_prepare(plx_q->sql->data, plx_q->nargs, types); err = SPI_execute_plan(plan, values, arg_nulls, true, 0); if (err != SPI_OK_SELECT) plx_error(plx_fn, "query '%s' failed: %s", plx_q->sql->data, SPI_result_code_string(err)); val = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull); err = SPI_finish(); if (err != SPI_OK_FINISH) plx_error(plx_fn, "SPI_finish: %s", SPI_result_code_string(err)); return DatumGetInt32(val); }
/* * Do compilation and execution under SPI. * * Result conversion will be done without SPI. */ static ProxyFunction * compile_and_execute(FunctionCallInfo fcinfo) { int err; ProxyFunction *func; ProxyCluster *cluster; /* prepare SPI */ err = SPI_connect(); if (err != SPI_OK_CONNECT) elog(ERROR, "SPI_connect: %s", SPI_result_code_string(err)); /* do the initialization also under SPI */ plproxy_startup_init(); /* compile code */ func = plproxy_compile_and_cache(fcinfo); /* get actual cluster to run on */ cluster = plproxy_find_cluster(func, fcinfo); /* Don't allow nested calls on the same cluster */ if (cluster->busy) plproxy_error(func, "Nested PL/Proxy calls to the same cluster are not supported."); /* fetch PGresults */ func->cur_cluster = cluster; plproxy_exec(func, fcinfo); /* done with SPI */ err = SPI_finish(); if (err != SPI_OK_FINISH) elog(ERROR, "SPI_finish: %s", SPI_result_code_string(err)); return func; }
static PyObject * PLy_spi_execute_query(char *query, long limit) { int rv; volatile MemoryContext oldcontext; volatile ResourceOwner oldowner; PyObject *ret = NULL; oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; PLy_spi_subtransaction_begin(oldcontext, oldowner); PG_TRY(); { PLyExecutionContext *exec_ctx = PLy_current_execution_context(); pg_verifymbstr(query, strlen(query), false); rv = SPI_execute(query, exec_ctx->curr_proc->fn_readonly, limit); ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); PLy_spi_subtransaction_commit(oldcontext, oldowner); } PG_CATCH(); { PLy_spi_subtransaction_abort(oldcontext, oldowner); return NULL; } PG_END_TRY(); if (rv < 0) { Py_XDECREF(ret); PLy_exception_set(PLy_exc_spi_error, "SPI_execute failed: %s", SPI_result_code_string(rv)); return NULL; } return ret; }
/* * Execute ProxyQuery locally. * * Result will be in SPI_tuptable. */ void plproxy_query_exec(ProxyFunction *func, FunctionCallInfo fcinfo, ProxyQuery *q, DatumArray **array_params, int array_row) { int i, idx, err; char arg_nulls[FUNC_MAX_ARGS]; Datum arg_values[FUNC_MAX_ARGS]; /* fill args */ for (i = 0; i < q->arg_count; i++) { idx = q->arg_lookup[i]; if (PG_ARGISNULL(idx)) { arg_nulls[i] = 'n'; arg_values[i] = (Datum) NULL; } else if (array_params && IS_SPLIT_ARG(func, idx)) { DatumArray *ats = array_params[idx]; arg_nulls[i] = ats->nulls[array_row] ? 'n' : ' '; arg_values[i] = ats->nulls[array_row] ? (Datum) NULL : ats->values[array_row]; } else { arg_nulls[i] = ' '; arg_values[i] = PG_GETARG_DATUM(idx); } } /* run query */ err = SPI_execute_plan(q->plan, arg_values, arg_nulls, true, 0); if (err != SPI_OK_SELECT) plproxy_error(func, "query '%s' failed: %s", q->sql, SPI_result_code_string(err)); }
Datum variant_typmod_in(PG_FUNCTION_ARGS) { ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0); Datum *elem_values; int arr_nelem; char *inputCString; Datum inputDatum; Datum out; Assert(fcinfo->flinfo->fn_strict); /* Must be strict */ deconstruct_array(arr, CSTRINGOID, -2, false, 'c', /* elmlen, elmbyval, elmalign */ &elem_values, NULL, &arr_nelem); /* elements, nulls, number_of_elements */ /* TODO: Sanity check array */ /* PointerGetDatum is equivalent to TextGetDatum, which doesn't exist */ inputCString = DatumGetCString(elem_values[0]); inputDatum = PointerGetDatum( cstring_to_text( inputCString ) ); /* TODO: cache this stuff */ /* Keep cruft localized to just here */ { bool do_pop = _SPI_conn(); bool isnull; int ret; Oid type = TEXTOID; /* This should arguably be FOR KEY SHARE. See comment in variant_get_variant_name() */ char *cmd = "SELECT variant_typmod, variant_enabled FROM variant._registered WHERE lower(variant_name) = lower($1)"; /* command, nargs, Oid *argument_types, *values, *nulls, read_only, count */ if( (ret = SPI_execute_with_args( cmd, 1, &type, &inputDatum, " ", true, 0 )) != SPI_OK_SELECT ) elog( ERROR, "SPI_execute_with_args(%s) returned %s", cmd, SPI_result_code_string(ret)); Assert( SPI_tuptable ); if ( SPI_processed > 1 ) ereport(ERROR, ( errmsg( "Got %u records for variant.variant(%s)", SPI_processed, inputCString ), errhint( "This means _variant._registered is corrupted" ) ) ); if ( SPI_processed < 1 ) elog( ERROR, "variant.variant(%s) is not registered", inputCString ); /* Note 0 vs 1 based numbering */ Assert(SPI_tuptable->tupdesc->attrs[0]->atttypid == INT4OID); Assert(SPI_tuptable->tupdesc->attrs[1]->atttypid == BOOLOID); out = heap_getattr( SPI_tuptable->vals[0], 2, SPI_tuptable->tupdesc, &isnull ); if( !DatumGetBool(out) ) ereport( ERROR, ( errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg( "variant.variant(%s) is disabled", inputCString ) ) ); /* Don't need to copy the tuple because int is pass by value */ out = heap_getattr( SPI_tuptable->vals[0], 1, SPI_tuptable->tupdesc, &isnull ); if( isnull ) ereport( ERROR, ( errmsg( "Found NULL variant_typmod for variant.variant(%s)", inputCString ), errhint( "This should never happen; is _variant._registered corrupted?" ) ) ); _SPI_disc(do_pop); } PG_RETURN_INT32(out); }
/* prepare(query="select * from foo") * prepare(query="select * from foo where bar = $1", params=["text"]) * prepare(query="select * from foo where bar = $1", params=["text"], limit=5) */ PyObject * PLy_spi_prepare(PyObject *self, PyObject *args) { PLyPlanObject *plan; PyObject *list = NULL; PyObject *volatile optr = NULL; char *query; volatile MemoryContext oldcontext; volatile ResourceOwner oldowner; volatile int nargs; if (!PyArg_ParseTuple(args, "s|O", &query, &list)) return NULL; if (list && (!PySequence_Check(list))) { PLy_exception_set(PyExc_TypeError, "second argument of plpy.prepare must be a sequence"); return NULL; } if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL) return NULL; nargs = list ? PySequence_Length(list) : 0; plan->nargs = nargs; plan->types = nargs ? PLy_malloc(sizeof(Oid) * nargs) : NULL; plan->values = nargs ? PLy_malloc(sizeof(Datum) * nargs) : NULL; plan->args = nargs ? PLy_malloc(sizeof(PLyTypeInfo) * nargs) : NULL; oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; PLy_spi_subtransaction_begin(oldcontext, oldowner); PG_TRY(); { int i; /* * the other loop might throw an exception, if PLyTypeInfo member * isn't properly initialized the Py_DECREF(plan) will go boom */ for (i = 0; i < nargs; i++) { PLy_typeinfo_init(&plan->args[i]); plan->values[i] = PointerGetDatum(NULL); } for (i = 0; i < nargs; i++) { char *sptr; HeapTuple typeTup; Oid typeId; int32 typmod; Form_pg_type typeStruct; optr = PySequence_GetItem(list, i); if (PyString_Check(optr)) sptr = PyString_AsString(optr); else if (PyUnicode_Check(optr)) sptr = PLyUnicode_AsString(optr); else { ereport(ERROR, (errmsg("plpy.prepare: type name at ordinal position %d is not a string", i))); sptr = NULL; /* keep compiler quiet */ } /******************************************************** * Resolve argument type names and then look them up by * oid in the system cache, and remember the required *information for input conversion. ********************************************************/ parseTypeString(sptr, &typeId, &typmod); typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeId)); if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", typeId); Py_DECREF(optr); /* * set optr to NULL, so we won't try to unref it again in case of * an error */ optr = NULL; plan->types[i] = typeId; typeStruct = (Form_pg_type) GETSTRUCT(typeTup); if (typeStruct->typtype != TYPTYPE_COMPOSITE) PLy_output_datum_func(&plan->args[i], typeTup); else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("plpy.prepare does not support composite types"))); ReleaseSysCache(typeTup); } pg_verifymbstr(query, strlen(query), false); plan->plan = SPI_prepare(query, plan->nargs, plan->types); if (plan->plan == NULL) elog(ERROR, "SPI_prepare failed: %s", SPI_result_code_string(SPI_result)); /* transfer plan from procCxt to topCxt */ if (SPI_keepplan(plan->plan)) elog(ERROR, "SPI_keepplan failed"); PLy_spi_subtransaction_commit(oldcontext, oldowner); } PG_CATCH(); { Py_DECREF(plan); Py_XDECREF(optr); PLy_spi_subtransaction_abort(oldcontext, oldowner); return NULL; } PG_END_TRY(); Assert(plan->plan != NULL); return (PyObject *) plan; }
static PyObject * PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit) { volatile int nargs; int i, rv; PLyPlanObject *plan; volatile MemoryContext oldcontext; volatile ResourceOwner oldowner; PyObject *ret; if (list != NULL) { if (!PySequence_Check(list) || PyString_Check(list) || PyUnicode_Check(list)) { PLy_exception_set(PyExc_TypeError, "plpy.execute takes a sequence as its second argument"); return NULL; } nargs = PySequence_Length(list); } else nargs = 0; plan = (PLyPlanObject *) ob; if (nargs != plan->nargs) { char *sv; PyObject *so = PyObject_Str(list); if (!so) PLy_elog(ERROR, "could not execute plan"); sv = PyString_AsString(so); PLy_exception_set_plural(PyExc_TypeError, "Expected sequence of %d argument, got %d: %s", "Expected sequence of %d arguments, got %d: %s", plan->nargs, plan->nargs, nargs, sv); Py_DECREF(so); return NULL; } oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; PLy_spi_subtransaction_begin(oldcontext, oldowner); PG_TRY(); { char *volatile nulls; volatile int j; if (nargs > 0) nulls = palloc(nargs * sizeof(char)); else nulls = NULL; for (j = 0; j < nargs; j++) { PyObject *elem; elem = PySequence_GetItem(list, j); if (elem != Py_None) { PG_TRY(); { plan->values[j] = plan->args[j].out.d.func(&(plan->args[j].out.d), -1, elem); } PG_CATCH(); { Py_DECREF(elem); PG_RE_THROW(); } PG_END_TRY(); Py_DECREF(elem); nulls[j] = ' '; } else { Py_DECREF(elem); plan->values[j] = InputFunctionCall(&(plan->args[j].out.d.typfunc), NULL, plan->args[j].out.d.typioparam, -1); nulls[j] = 'n'; } } rv = SPI_execute_plan(plan->plan, plan->values, nulls, PLy_curr_procedure->fn_readonly, limit); ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); if (nargs > 0) pfree(nulls); PLy_spi_subtransaction_commit(oldcontext, oldowner); } PG_CATCH(); { int k; /* * cleanup plan->values array */ for (k = 0; k < nargs; k++) { if (!plan->args[k].out.d.typbyval && (plan->values[k] != PointerGetDatum(NULL))) { pfree(DatumGetPointer(plan->values[k])); plan->values[k] = PointerGetDatum(NULL); } } PLy_spi_subtransaction_abort(oldcontext, oldowner); return NULL; } PG_END_TRY(); for (i = 0; i < nargs; i++) { if (!plan->args[i].out.d.typbyval && (plan->values[i] != PointerGetDatum(NULL))) { pfree(DatumGetPointer(plan->values[i])); plan->values[i] = PointerGetDatum(NULL); } } if (rv < 0) { PLy_exception_set(PLy_exc_spi_error, "SPI_execute_plan failed: %s", SPI_result_code_string(rv)); return NULL; } return ret; }
Datum ttdummy(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger; /* to get trigger name */ char **args; /* arguments */ int attnum[2]; /* fnumbers of start/stop columns */ Datum oldon, oldoff; Datum newon, newoff; Datum *cvals; /* column values */ char *cnulls; /* column nulls */ char *relname; /* triggered relation name */ Relation rel; /* triggered relation */ HeapTuple trigtuple; HeapTuple newtuple = NULL; HeapTuple rettuple; TupleDesc tupdesc; /* tuple description */ int natts; /* # of attributes */ bool isnull; /* to know is some column NULL or not */ int ret; int i; if (!CALLED_AS_TRIGGER(fcinfo)) elog(ERROR, "ttdummy: not fired by trigger manager"); if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) elog(ERROR, "ttdummy: must be fired for row"); if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) elog(ERROR, "ttdummy: must be fired before event"); if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) elog(ERROR, "ttdummy: cannot process INSERT event"); if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) newtuple = trigdata->tg_newtuple; trigtuple = trigdata->tg_trigtuple; rel = trigdata->tg_relation; relname = SPI_getrelname(rel); /* check if TT is OFF for this relation */ if (ttoff) /* OFF - nothing to do */ { pfree(relname); return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple); } trigger = trigdata->tg_trigger; if (trigger->tgnargs != 2) elog(ERROR, "ttdummy (%s): invalid (!= 2) number of arguments %d", relname, trigger->tgnargs); args = trigger->tgargs; tupdesc = rel->rd_att; natts = tupdesc->natts; for (i = 0; i < 2; i++) { attnum[i] = SPI_fnumber(tupdesc, args[i]); if (attnum[i] <= 0) elog(ERROR, "ttdummy (%s): there is no attribute %s", relname, args[i]); if (SPI_gettypeid(tupdesc, attnum[i]) != INT4OID) elog(ERROR, "ttdummy (%s): attribute %s must be of integer type", relname, args[i]); } oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull); if (isnull) elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[0]); oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull); if (isnull) elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[1]); if (newtuple != NULL) /* UPDATE */ { newon = SPI_getbinval(newtuple, tupdesc, attnum[0], &isnull); if (isnull) elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[0]); newoff = SPI_getbinval(newtuple, tupdesc, attnum[1], &isnull); if (isnull) elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[1]); if (oldon != newon || oldoff != newoff) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("ttdummy (%s): you cannot change %s and/or %s columns (use set_ttdummy)", relname, args[0], args[1]))); if (newoff != TTDUMMY_INFINITY) { pfree(relname); /* allocated in upper executor context */ return PointerGetDatum(NULL); } } else if (oldoff != TTDUMMY_INFINITY) /* DELETE */ { pfree(relname); return PointerGetDatum(NULL); } newoff = DirectFunctionCall1(nextval, CStringGetTextDatum("ttdummy_seq")); /* nextval now returns int64; coerce down to int32 */ newoff = Int32GetDatum((int32) DatumGetInt64(newoff)); /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) elog(ERROR, "ttdummy (%s): SPI_connect returned %d", relname, ret); /* Fetch tuple values and nulls */ cvals = (Datum *) palloc(natts * sizeof(Datum)); cnulls = (char *) palloc(natts * sizeof(char)); for (i = 0; i < natts; i++) { cvals[i] = SPI_getbinval((newtuple != NULL) ? newtuple : trigtuple, tupdesc, i + 1, &isnull); cnulls[i] = (isnull) ? 'n' : ' '; } /* change date column(s) */ if (newtuple) /* UPDATE */ { cvals[attnum[0] - 1] = newoff; /* start_date eq current date */ cnulls[attnum[0] - 1] = ' '; cvals[attnum[1] - 1] = TTDUMMY_INFINITY; /* stop_date eq INFINITY */ cnulls[attnum[1] - 1] = ' '; } else /* DELETE */ { cvals[attnum[1] - 1] = newoff; /* stop_date eq current date */ cnulls[attnum[1] - 1] = ' '; } /* if there is no plan ... */ if (splan == NULL) { SPIPlanPtr pplan; Oid *ctypes; char *query; /* allocate space in preparation */ ctypes = (Oid *) palloc(natts * sizeof(Oid)); query = (char *) palloc(100 + 16 * natts); /* * Construct query: INSERT INTO _relation_ VALUES ($1, ...) */ sprintf(query, "INSERT INTO %s VALUES (", relname); for (i = 1; i <= natts; i++) { sprintf(query + strlen(query), "$%d%s", i, (i < natts) ? ", " : ")"); ctypes[i - 1] = SPI_gettypeid(tupdesc, i); } /* Prepare plan for query */ pplan = SPI_prepare(query, natts, ctypes); if (pplan == NULL) elog(ERROR, "ttdummy (%s): SPI_prepare returned %s", relname, SPI_result_code_string(SPI_result)); if (SPI_keepplan(pplan)) elog(ERROR, "ttdummy (%s): SPI_keepplan failed", relname); splan = pplan; } ret = SPI_execp(splan, cvals, cnulls, 0); if (ret < 0) elog(ERROR, "ttdummy (%s): SPI_execp returned %d", relname, ret); /* Tuple to return to upper Executor ... */ if (newtuple) /* UPDATE */ rettuple = SPI_modifytuple(rel, trigtuple, 1, &(attnum[1]), &newoff, NULL); else /* DELETE */ rettuple = trigtuple; SPI_finish(); /* don't forget say Bye to SPI mgr */ pfree(relname); return PointerGetDatum(rettuple); }
static PyObject * PLy_cursor_plan(PyObject *ob, PyObject *args) { PLyCursorObject *cursor; volatile int nargs; int i; PLyPlanObject *plan; volatile MemoryContext oldcontext; volatile ResourceOwner oldowner; if (args) { if (!PySequence_Check(args) || PyString_Check(args) || PyUnicode_Check(args)) { PLy_exception_set(PyExc_TypeError, "plpy.cursor takes a sequence as its second argument"); return NULL; } nargs = PySequence_Length(args); } else nargs = 0; plan = (PLyPlanObject *) ob; if (nargs != plan->nargs) { char *sv; PyObject *so = PyObject_Str(args); if (!so) PLy_elog(ERROR, "could not execute plan"); sv = PyString_AsString(so); PLy_exception_set_plural(PyExc_TypeError, "Expected sequence of %d argument, got %d: %s", "Expected sequence of %d arguments, got %d: %s", plan->nargs, plan->nargs, nargs, sv); Py_DECREF(so); return NULL; } if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL) return NULL; cursor->portalname = NULL; cursor->closed = false; cursor->mcxt = AllocSetContextCreate(TopMemoryContext, "PL/Python cursor context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); PLy_typeinfo_init(&cursor->result, cursor->mcxt); oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; PLy_spi_subtransaction_begin(oldcontext, oldowner); PG_TRY(); { PLyExecutionContext *exec_ctx = PLy_current_execution_context(); Portal portal; char *volatile nulls; volatile int j; if (nargs > 0) nulls = palloc(nargs * sizeof(char)); else nulls = NULL; for (j = 0; j < nargs; j++) { PyObject *elem; elem = PySequence_GetItem(args, j); if (elem != Py_None) { PG_TRY(); { plan->values[j] = plan->args[j].out.d.func(&(plan->args[j].out.d), -1, elem); } PG_CATCH(); { Py_DECREF(elem); PG_RE_THROW(); } PG_END_TRY(); Py_DECREF(elem); nulls[j] = ' '; } else { Py_DECREF(elem); plan->values[j] = InputFunctionCall(&(plan->args[j].out.d.typfunc), NULL, plan->args[j].out.d.typioparam, -1); nulls[j] = 'n'; } } portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls, exec_ctx->curr_proc->fn_readonly); if (portal == NULL) elog(ERROR, "SPI_cursor_open() failed: %s", SPI_result_code_string(SPI_result)); cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name); PLy_spi_subtransaction_commit(oldcontext, oldowner); } PG_CATCH(); { int k; /* cleanup plan->values array */ for (k = 0; k < nargs; k++) { if (!plan->args[k].out.d.typbyval && (plan->values[k] != PointerGetDatum(NULL))) { pfree(DatumGetPointer(plan->values[k])); plan->values[k] = PointerGetDatum(NULL); } } Py_DECREF(cursor); PLy_spi_subtransaction_abort(oldcontext, oldowner); return NULL; } PG_END_TRY(); for (i = 0; i < nargs; i++) { if (!plan->args[i].out.d.typbyval && (plan->values[i] != PointerGetDatum(NULL))) { pfree(DatumGetPointer(plan->values[i])); plan->values[i] = PointerGetDatum(NULL); } } Assert(cursor->portalname != NULL); return (PyObject *) cursor; }
Datum plpgsql_validator(PG_FUNCTION_ARGS) { Oid funcoid = PG_GETARG_OID(0); HeapTuple tuple; Form_pg_proc proc; char functyptype; int numargs; Oid *argtypes; char **argnames; char *argmodes; bool istrigger = false; int i; if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) PG_RETURN_VOID(); /* Get the new function's pg_proc entry */ tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(funcoid), 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcoid); proc = (Form_pg_proc) GETSTRUCT(tuple); functyptype = get_typtype(proc->prorettype); /* Disallow pseudotype result */ /* except for TRIGGER, RECORD, VOID, or polymorphic */ if (functyptype == TYPTYPE_PSEUDO) { /* we assume OPAQUE with no arguments means a trigger */ if (proc->prorettype == TRIGGEROID || (proc->prorettype == OPAQUEOID && proc->pronargs == 0)) istrigger = true; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && !IsPolymorphicType(proc->prorettype)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/pgSQL functions cannot return type %s", format_type_be(proc->prorettype)))); } /* Disallow pseudotypes in arguments (either IN or OUT) */ /* except for polymorphic */ numargs = get_func_arg_info(tuple, &argtypes, &argnames, &argmodes); for (i = 0; i < numargs; i++) { if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO) { if (!IsPolymorphicType(argtypes[i])) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/pgSQL functions cannot accept type %s", format_type_be(argtypes[i])))); } } /* Postpone body checks if !check_function_bodies */ if (check_function_bodies) { FunctionCallInfoData fake_fcinfo; FmgrInfo flinfo; TriggerData trigdata; int rc; /* * Connect to SPI manager (is this needed for compilation?) */ if ((rc = SPI_connect()) != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc)); /* * Set up a fake fcinfo with just enough info to satisfy * plpgsql_compile(). */ MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo)); MemSet(&flinfo, 0, sizeof(flinfo)); fake_fcinfo.flinfo = &flinfo; flinfo.fn_oid = funcoid; flinfo.fn_mcxt = CurrentMemoryContext; if (istrigger) { MemSet(&trigdata, 0, sizeof(trigdata)); trigdata.type = T_TriggerData; fake_fcinfo.context = (Node *) &trigdata; } /* Test-compile the function */ plpgsql_compile(&fake_fcinfo, true); /* * Disconnect from SPI manager */ if ((rc = SPI_finish()) != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc)); } ReleaseSysCache(tuple); PG_RETURN_VOID(); }
static void test_spi_exec_query(int *passed, int *total) { int rc; volatile int caseno = 0; SPIPlanPtr ptr = NULL; SPIPlanPtr org_ptr; /* Initialize */ rc = SPI_connect(); if (rc != SPI_OK_CONNECT) elog(ERROR, "could not connect SPI: %s", SPI_result_code_string(rc)); /* * *-*-1 * - plan is not cached */ caseno++; BeginInternalSubTransaction("test"); PG_TRY(); { spi_exec_query("SELECT 1", 0, NULL, &ptr, NULL, NULL, SPI_OK_SELECT); if (ptr != NULL && SPI_processed == 1) { elog(WARNING, "%s-%d ok", __FUNCTION__, caseno); (*passed)++; } ReleaseCurrentSubTransaction(); } PG_CATCH(); { elog(WARNING, "*-*-%d failed", caseno); RollbackAndReleaseCurrentSubTransaction(); SPI_restore_connection(); } PG_END_TRY(); /* * *-*-2 * - plan is cached */ caseno++; BeginInternalSubTransaction("test"); PG_TRY(); { org_ptr = ptr; spi_exec_query(NULL, 0, NULL, &ptr, NULL, NULL, SPI_OK_SELECT); if (ptr == org_ptr && SPI_processed == 1) { elog(WARNING, "%s-%d ok", __FUNCTION__, caseno); (*passed)++; } ReleaseCurrentSubTransaction(); } PG_CATCH(); { elog(WARNING, "*-*-%d failed", caseno); RollbackAndReleaseCurrentSubTransaction(); FlushErrorState(); SPI_restore_connection(); } PG_END_TRY(); SPI_freeplan(ptr); ptr = NULL; /* * *-*-3 * - query error */ caseno++; BeginInternalSubTransaction("test"); PG_TRY(); { spi_exec_query("SELECT 1 / 0", 0, NULL, &ptr, NULL, NULL, SPI_OK_SELECT); elog(WARNING, "*-*-%d failed", caseno); ReleaseCurrentSubTransaction(); } PG_CATCH(); { elog(WARNING, "%s-%d ok", __FUNCTION__, caseno); (*passed)++; RollbackAndReleaseCurrentSubTransaction(); FlushErrorState(); SPI_restore_connection(); } PG_END_TRY(); SPI_freeplan(ptr); ptr = NULL; /* * *-*-4 * - query success */ caseno++; BeginInternalSubTransaction("test"); PG_TRY(); { spi_exec_query("SELECT 1", 0, NULL, &ptr, NULL, NULL, SPI_OK_SELECT); if (ptr != NULL && SPI_processed == 1) { elog(WARNING, "%s-%d ok", __FUNCTION__, caseno); (*passed)++; } ReleaseCurrentSubTransaction(); } PG_CATCH(); { elog(WARNING, "*-*-%d failed", caseno); PG_RE_THROW(); RollbackAndReleaseCurrentSubTransaction(); SPI_restore_connection(); } PG_END_TRY(); SPI_freeplan(ptr); ptr = NULL; /* report # of tests */ (*total) += caseno; /* Cleanup */ rc = SPI_finish(); if (rc != SPI_OK_FINISH && rc != SPI_ERROR_UNCONNECTED) elog(ERROR, "could not finish SPI: %s", SPI_result_code_string(rc)); }
/* * variant_cmp_int: Compare two variants */ static int variant_cmp_int(FunctionCallInfo fcinfo) { Variant l, r; VariantInt li; VariantInt ri; int out; Assert(fcinfo->flinfo->fn_strict); /* Must not be callable on NULL input */ l = PG_GETARG_VARIANT(0); r = PG_GETARG_VARIANT(1); /* * Presumably if both inputs are binary equal then they are in fact equal. * The only problem is two variants storing NULL would be binary equal but * can't be treated as-such. Given that issue, it doesn't seem worth trying * to optimize this. * * Note that we're not trying to play tricks with not detoasting or * un-packing, unlike variant_image_eq(). */ #ifdef NOT_USED if(VARSIZE(l) == VARSIZE(r) && memcmp(l, r, VARSIZE(l)) == 0) return 0; #endif /* * We don't care about IO function but must specify something * * TODO: Improve caching so it will handle more than just one type :( */ li = make_variant_int(l, fcinfo, IOFunc_input); ri = make_variant_int(r, fcinfo, IOFunc_input); /* * We need to special-case IS DISTINCT, because it considers NULL to be the * same as NULL. */ if(fcinfo->flinfo->fn_expr && IsA(fcinfo->flinfo->fn_expr, DistinctExpr)) { if( li->isnull && ri->isnull ) return 0; else if( li->isnull || ri->isnull ) return -1; } else if(li->isnull || ri->isnull ) PG_RETURN_NULL(); /* TODO: If both variants are of the same type try comparing directly */ /* TODO: Support Transform_null_equals */ /* Do comparison via SPI */ /* TODO: cache this */ { bool do_pop; int ret; char *cmd; Oid types[2]; Datum values[2]; bool nulls[2]; do_pop = _SPI_conn(); cmd = "SELECT CASE WHEN $1 = $2 THEN 0 WHEN $1 < $2 THEN -1 ELSE 1 END::int"; types[0] = li->typid; values[0] = li->data; nulls[0] = li->isnull; types[1] = ri->typid; values[1] = ri->data; nulls[1] = ri->isnull; if( (ret = SPI_execute_with_args( cmd, 2, types, values, nulls, true, /* read-only */ 0 /* count */ )) != SPI_OK_SELECT ) elog( ERROR, "SPI_execute_with_args returned %s", SPI_result_code_string(ret)); /* Note 0 vs 1 based numbering */ Assert(SPI_tuptable->tupdesc->attrs[0]->atttypid == INT4OID); /* Don't need to copy the tuple because int is pass by value */ out = DatumGetInt32( heap_getattr(SPI_tuptable->vals[0], 1, SPI_tuptable->tupdesc, &fcinfo->isnull) ); _SPI_disc(do_pop); } return out; }
/* * variant_get_variant_name: Return the name of a named variant */ char * variant_get_variant_name(int typmod, Oid org_typid, bool ignore_storage) { Datum values[2]; MemoryContext cctx = CurrentMemoryContext; bool do_pop = _SPI_conn(); bool isnull; Oid types[2] = {INT4OID, REGTYPEOID}; char *cmd; int nargs; int ret; Datum result; char *out; values[0] = Int32GetDatum(typmod); /* * There's a race condition here; someone could be attempting to remove an * allowed type from this registered variant or even remove it entirely. We * could avoid that by taking a share/keyshare lock here and taking the * appropriate blocking lock when modifying the registration record. Doing * that would probably be quite bad though; not only are type IO and typmod * IO routines assumed to be non-volatile, taking such a lock would end up * generating a lot of lock updates to the registration rows. * * Since the whole purpose of registration is to handle the issue of someone * attempting to drop a type that has made it into a variant in a table * column, which we can't completely handle anyway, I don't think it's worth * it to lock the rows. */ if(ignore_storage) { cmd = "SELECT variant_name, variant_enabled, storage_allowed FROM variant._registered WHERE variant_typmod = $1"; nargs = 1; } else { cmd = "SELECT variant_name, variant_enabled, storage_allowed, allowed_types @> array[ $2 ] FROM variant._registered WHERE variant_typmod = $1"; nargs = 2; values[1] = ObjectIdGetDatum(org_typid); } /* command, nargs, Oid *argument_types, *values, *nulls, read_only, count */ if( (ret = SPI_execute_with_args( cmd, nargs, types, values, " ", true, 0 )) != SPI_OK_SELECT ) elog( ERROR, "SPI_execute_with_args(%s) returned %s", cmd, SPI_result_code_string(ret)); Assert( SPI_tuptable ); if ( SPI_processed > 1 ) ereport(ERROR, ( errmsg( "Got %u records for variant typmod %i", SPI_processed, typmod ), errhint( "This means _variant._registered is corrupted" ) ) ); if ( SPI_processed < 1 ) elog( ERROR, "invalid typmod %i", typmod ); /* Note 0 vs 1 based numbering */ Assert(SPI_tuptable->tupdesc->attrs[0]->atttypid == VARCHAROID); Assert(SPI_tuptable->tupdesc->attrs[1]->atttypid == BOOLOID); Assert(SPI_tuptable->tupdesc->attrs[2]->atttypid == BOOLOID); result = heap_getattr( SPI_tuptable->vals[0], 1, SPI_tuptable->tupdesc, &isnull ); if( isnull ) ereport( ERROR, ( errmsg( "Found NULL variant_name for typmod %i", typmod ), errhint( "This should never happen; is _variant._registered corrupted?" ) ) ); MemoryContextSwitchTo(cctx); out = text_to_cstring(DatumGetTextP(result)); result = heap_getattr( SPI_tuptable->vals[0], 2, SPI_tuptable->tupdesc, &isnull ); if( !DatumGetBool(result) ) ereport( ERROR, ( errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg( "variant.variant(%s) is disabled", out ) ) ); /* * If storage is allowed, then throw an error if we don't know what our * original type is, or if that type is not listed as allowed. */ if(!ignore_storage) { result = heap_getattr( SPI_tuptable->vals[0], 3, SPI_tuptable->tupdesc, &isnull ); if( DatumGetBool(result) ) { if( org_typid == InvalidOid) ereport( ERROR, ( errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg( "Unable to determine original type" ) ) ); result = heap_getattr( SPI_tuptable->vals[0], 4, SPI_tuptable->tupdesc, &isnull ); if( !DatumGetBool(result) ) ereport( ERROR, ( errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg( "type %s is not allowed in variant.variant(%s)", format_type_be(org_typid), out ), errhint( "you can permanently allow a type to be used by calling variant.allow_type()" ) ) ); } } _SPI_disc(do_pop); /* pfree's all SPI stuff */ return out; }
static void test_spi_exec_utility(int *passed, int *total) { int rc; volatile int caseno = 0; /* Initialize */ rc = SPI_connect(); if (rc != SPI_OK_CONNECT) elog(ERROR, "could not connect SPI: %s", SPI_result_code_string(rc)); /* * *-*-1 * - query error */ caseno++; BeginInternalSubTransaction("test"); PG_TRY(); { spi_exec_utility("RESET dummy_parameter"); elog(WARNING, "*-*-%d failed", caseno); ReleaseCurrentSubTransaction(); } PG_CATCH(); { elog(WARNING, "%s-%d ok", __FUNCTION__, caseno); (*passed)++; RollbackAndReleaseCurrentSubTransaction(); FlushErrorState(); SPI_restore_connection(); } PG_END_TRY(); /* * *-*-2 * - query success */ caseno++; BeginInternalSubTransaction("test"); PG_TRY(); { spi_exec_utility("RESET client_min_messages"); elog(WARNING, "%s-%d ok", __FUNCTION__, caseno); (*passed)++; ReleaseCurrentSubTransaction(); } PG_CATCH(); { elog(WARNING, "*-*-%d failed", caseno); RollbackAndReleaseCurrentSubTransaction(); SPI_restore_connection(); } PG_END_TRY(); /* report # of tests */ (*total) += caseno; /* Cleanup */ rc = SPI_finish(); if (rc != SPI_OK_FINISH && rc != SPI_ERROR_UNCONNECTED) elog(ERROR, "could not finish SPI: %s", SPI_result_code_string(rc)); }
Datum plpgsql_inline_handler(PG_FUNCTION_ARGS) { InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0)); PLpgSQL_function *func; FunctionCallInfoData fake_fcinfo; FmgrInfo flinfo; EState *simple_eval_estate; Datum retval; int rc; Assert(IsA(codeblock, InlineCodeBlock)); /* * Connect to SPI manager */ if ((rc = SPI_connect()) != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc)); /* Compile the anonymous code block */ func = plpgsql_compile_inline(codeblock->source_text); /* Mark the function as busy, just pro forma */ func->use_count++; /* * Set up a fake fcinfo with just enough info to satisfy * plpgsql_exec_function(). In particular note that this sets things up * with no arguments passed. */ MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo)); MemSet(&flinfo, 0, sizeof(flinfo)); fake_fcinfo.flinfo = &flinfo; flinfo.fn_oid = InvalidOid; flinfo.fn_mcxt = CurrentMemoryContext; /* Create a private EState for simple-expression execution */ simple_eval_estate = CreateExecutorState(); /* And run the function */ PG_TRY(); { retval = plpgsql_exec_function(func, &fake_fcinfo, simple_eval_estate); } PG_CATCH(); { /* * We need to clean up what would otherwise be long-lived resources * accumulated by the failed DO block, principally cached plans for * statements (which can be flushed with plpgsql_free_function_memory) * and execution trees for simple expressions, which are in the * private EState. * * Before releasing the private EState, we must clean up any * simple_econtext_stack entries pointing into it, which we can do by * invoking the subxact callback. (It will be called again later if * some outer control level does a subtransaction abort, but no harm * is done.) We cheat a bit knowing that plpgsql_subxact_cb does not * pay attention to its parentSubid argument. */ plpgsql_subxact_cb(SUBXACT_EVENT_ABORT_SUB, GetCurrentSubTransactionId(), 0, NULL); /* Clean up the private EState */ FreeExecutorState(simple_eval_estate); /* Function should now have no remaining use-counts ... */ func->use_count--; Assert(func->use_count == 0); /* ... so we can free subsidiary storage */ plpgsql_free_function_memory(func); /* And propagate the error */ PG_RE_THROW(); } PG_END_TRY(); /* Clean up the private EState */ FreeExecutorState(simple_eval_estate); /* Function should now have no remaining use-counts ... */ func->use_count--; Assert(func->use_count == 0); /* ... so we can free subsidiary storage */ plpgsql_free_function_memory(func); /* * Disconnect from SPI manager */ if ((rc = SPI_finish()) != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc)); return retval; }
/* ---------- * Generate a prepared plan - this is copy from pl_exec.c * ---------- */ static void exec_prepare_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, int cursorOptions) { #if PG_VERSION_NUM >= 90000 SPIPlanPtr plan; /* * The grammar can't conveniently set expr->func while building the parse * tree, so make sure it's set before parser hooks need it. */ expr->func = estate->func; /* * Generate and save the plan */ plan = SPI_prepare_params(expr->query, (ParserSetupHook) plpgsql_parser_setup, (void *) expr, cursorOptions); if (plan == NULL) { /* Some SPI errors deserve specific error messages */ switch (SPI_result) { case SPI_ERROR_COPY: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot COPY to/from client in PL/pgSQL"))); case SPI_ERROR_TRANSACTION: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot begin/end transactions in PL/pgSQL"), errhint("Use a BEGIN block with an EXCEPTION clause instead."))); default: elog(ERROR, "SPI_prepare_params failed for \"%s\": %s", expr->query, SPI_result_code_string(SPI_result)); } } expr->plan = SPI_saveplan(plan); SPI_freeplan(plan); #else int i; SPIPlanPtr plan; Oid *argtypes; /* * We need a temporary argtypes array to load with data. (The finished * plan structure will contain a copy of it.) */ argtypes = (Oid *) palloc(expr->nparams * sizeof(Oid)); for (i = 0; i < expr->nparams; i++) { argtypes[i] = exec_get_datum_type(estate, estate->datums[expr->params[i]]); } /* * Generate and save the plan */ plan = SPI_prepare_cursor(expr->query, expr->nparams, argtypes, cursorOptions); if (plan == NULL) { /* Some SPI errors deserve specific error messages */ switch (SPI_result) { case SPI_ERROR_COPY: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot COPY to/from client in PL/pgSQL"))); case SPI_ERROR_TRANSACTION: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot begin/end transactions in PL/pgSQL"), errhint("Use a BEGIN block with an EXCEPTION clause instead."))); default: elog(ERROR, "SPI_prepare_cursor failed for \"%s\": %s", expr->query, SPI_result_code_string(SPI_result)); } } expr->plan = SPI_saveplan(plan); SPI_freeplan(plan); pfree(argtypes); #endif }
static HeapTuple plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup) { SV **svp; HV *hvNew; HeapTuple rtup; SV *val; char *key; I32 klen; int slotsused; int *modattrs; Datum *modvalues; char *modnulls; TupleDesc tupdesc; tupdesc = tdata->tg_relation->rd_att; svp = hv_fetch(hvTD, "new", 3, FALSE); if (!svp) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("$_TD->{new} does not exist"))); if (!SvOK(*svp) || SvTYPE(*svp) != SVt_RV || SvTYPE(SvRV(*svp)) != SVt_PVHV) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("$_TD->{new} is not a hash reference"))); hvNew = (HV *) SvRV(*svp); modattrs = palloc(tupdesc->natts * sizeof(int)); modvalues = palloc(tupdesc->natts * sizeof(Datum)); modnulls = palloc(tupdesc->natts * sizeof(char)); slotsused = 0; hv_iterinit(hvNew); while ((val = hv_iternextsv(hvNew, &key, &klen))) { int attn = SPI_fnumber(tupdesc, key); if (attn <= 0 || tupdesc->attrs[attn - 1]->attisdropped) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("Perl hash contains nonexistent column \"%s\"", key))); if (SvOK(val) && SvTYPE(val) != SVt_NULL) { Oid typinput; Oid typioparam; FmgrInfo finfo; /* XXX would be better to cache these lookups */ getTypeInputInfo(tupdesc->attrs[attn - 1]->atttypid, &typinput, &typioparam); fmgr_info(typinput, &finfo); modvalues[slotsused] = FunctionCall3(&finfo, CStringGetDatum(SvPV(val, PL_na)), ObjectIdGetDatum(typioparam), Int32GetDatum(tupdesc->attrs[attn - 1]->atttypmod)); modnulls[slotsused] = ' '; } else { modvalues[slotsused] = (Datum) 0; modnulls[slotsused] = 'n'; } modattrs[slotsused] = attn; slotsused++; } hv_iterinit(hvNew); rtup = SPI_modifytuple(tdata->tg_relation, otup, slotsused, modattrs, modvalues, modnulls); pfree(modattrs); pfree(modvalues); pfree(modnulls); if (rtup == NULL) elog(ERROR, "SPI_modifytuple failed: %s", SPI_result_code_string(SPI_result)); return rtup; }
Datum /* have to return HeapTuple to Executor */ timetravel(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger; /* to get trigger name */ int argc; char **args; /* arguments */ int attnum[MaxAttrNum]; /* fnumbers of start/stop columns */ Datum oldtimeon, oldtimeoff; Datum newtimeon, newtimeoff, newuser, nulltext; Datum *cvals; /* column values */ char *cnulls; /* column nulls */ char *relname; /* triggered relation name */ Relation rel; /* triggered relation */ HeapTuple trigtuple; HeapTuple newtuple = NULL; HeapTuple rettuple; TupleDesc tupdesc; /* tuple description */ int natts; /* # of attributes */ EPlan *plan; /* prepared plan */ char ident[2 * NAMEDATALEN]; bool isnull; /* to know is some column NULL or not */ bool isinsert = false; int ret; int i; /* * Some checks first... */ /* Called by trigger manager ? */ if (!CALLED_AS_TRIGGER(fcinfo)) elog(ERROR, "timetravel: not fired by trigger manager"); /* Should be called for ROW trigger */ if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) elog(ERROR, "timetravel: must be fired for row"); /* Should be called BEFORE */ if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) elog(ERROR, "timetravel: must be fired before event"); /* INSERT ? */ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) isinsert = true; if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) newtuple = trigdata->tg_newtuple; trigtuple = trigdata->tg_trigtuple; rel = trigdata->tg_relation; relname = SPI_getrelname(rel); /* check if TT is OFF for this relation */ if (0 == findTTStatus(relname)) { /* OFF - nothing to do */ pfree(relname); return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple); } trigger = trigdata->tg_trigger; argc = trigger->tgnargs; if (argc != MinAttrNum && argc != MaxAttrNum) elog(ERROR, "timetravel (%s): invalid (!= %d or %d) number of arguments %d", relname, MinAttrNum, MaxAttrNum, trigger->tgnargs); args = trigger->tgargs; tupdesc = rel->rd_att; natts = tupdesc->natts; for (i = 0; i < MinAttrNum; i++) { attnum[i] = SPI_fnumber(tupdesc, args[i]); if (attnum[i] <= 0) elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]); if (SPI_gettypeid(tupdesc, attnum[i]) != ABSTIMEOID) elog(ERROR, "timetravel (%s): attribute %s must be of abstime type", relname, args[i]); } for (; i < argc; i++) { attnum[i] = SPI_fnumber(tupdesc, args[i]); if (attnum[i] <= 0) elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]); if (SPI_gettypeid(tupdesc, attnum[i]) != TEXTOID) elog(ERROR, "timetravel (%s): attribute %s must be of text type", relname, args[i]); } /* create fields containing name */ newuser = CStringGetTextDatum(GetUserNameFromId(GetUserId(), false)); nulltext = (Datum) NULL; if (isinsert) { /* INSERT */ int chnattrs = 0; int chattrs[MaxAttrNum]; Datum newvals[MaxAttrNum]; bool newnulls[MaxAttrNum]; oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull); if (isnull) { newvals[chnattrs] = GetCurrentAbsoluteTime(); newnulls[chnattrs] = false; chattrs[chnattrs] = attnum[a_time_on]; chnattrs++; } oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull); if (isnull) { if ((chnattrs == 0 && DatumGetInt32(oldtimeon) >= NOEND_ABSTIME) || (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) >= NOEND_ABSTIME)) elog(ERROR, "timetravel (%s): %s is infinity", relname, args[a_time_on]); newvals[chnattrs] = NOEND_ABSTIME; newnulls[chnattrs] = false; chattrs[chnattrs] = attnum[a_time_off]; chnattrs++; } else { if ((chnattrs == 0 && DatumGetInt32(oldtimeon) > DatumGetInt32(oldtimeoff)) || (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) > DatumGetInt32(oldtimeoff))) elog(ERROR, "timetravel (%s): %s gt %s", relname, args[a_time_on], args[a_time_off]); } pfree(relname); if (chnattrs <= 0) return PointerGetDatum(trigtuple); if (argc == MaxAttrNum) { /* clear update_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = true; chattrs[chnattrs] = attnum[a_upd_user]; chnattrs++; /* clear delete_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = true; chattrs[chnattrs] = attnum[a_del_user]; chnattrs++; /* set insert_user value */ newvals[chnattrs] = newuser; newnulls[chnattrs] = false; chattrs[chnattrs] = attnum[a_ins_user]; chnattrs++; } rettuple = heap_modify_tuple_by_cols(trigtuple, tupdesc, chnattrs, chattrs, newvals, newnulls); return PointerGetDatum(rettuple); /* end of INSERT */ } /* UPDATE/DELETE: */ oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]); oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]); /* * If DELETE/UPDATE of tuple with stop_date neq INFINITY then say upper * Executor to skip operation for this tuple */ if (newtuple != NULL) { /* UPDATE */ newtimeon = SPI_getbinval(newtuple, tupdesc, attnum[a_time_on], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]); newtimeoff = SPI_getbinval(newtuple, tupdesc, attnum[a_time_off], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]); if (oldtimeon != newtimeon || oldtimeoff != newtimeoff) elog(ERROR, "timetravel (%s): you cannot change %s and/or %s columns (use set_timetravel)", relname, args[a_time_on], args[a_time_off]); } if (oldtimeoff != NOEND_ABSTIME) { /* current record is a deleted/updated record */ pfree(relname); return PointerGetDatum(NULL); } newtimeoff = GetCurrentAbsoluteTime(); /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) elog(ERROR, "timetravel (%s): SPI_connect returned %d", relname, ret); /* Fetch tuple values and nulls */ cvals = (Datum *) palloc(natts * sizeof(Datum)); cnulls = (char *) palloc(natts * sizeof(char)); for (i = 0; i < natts; i++) { cvals[i] = SPI_getbinval(trigtuple, tupdesc, i + 1, &isnull); cnulls[i] = (isnull) ? 'n' : ' '; } /* change date column(s) */ cvals[attnum[a_time_off] - 1] = newtimeoff; /* stop_date eq current date */ cnulls[attnum[a_time_off] - 1] = ' '; if (!newtuple) { /* DELETE */ if (argc == MaxAttrNum) { cvals[attnum[a_del_user] - 1] = newuser; /* set delete user */ cnulls[attnum[a_del_user] - 1] = ' '; } } /* * Construct ident string as TriggerName $ TriggeredRelationId and try to * find prepared execution plan. */ snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id); plan = find_plan(ident, &Plans, &nPlans); /* if there is no plan ... */ if (plan->splan == NULL) { SPIPlanPtr pplan; Oid *ctypes; char sql[8192]; char separ = ' '; /* allocate ctypes for preparation */ ctypes = (Oid *) palloc(natts * sizeof(Oid)); /* * Construct query: INSERT INTO _relation_ VALUES ($1, ...) */ snprintf(sql, sizeof(sql), "INSERT INTO %s VALUES (", relname); for (i = 1; i <= natts; i++) { ctypes[i - 1] = SPI_gettypeid(tupdesc, i); if (!(TupleDescAttr(tupdesc, i - 1)->attisdropped)) /* skip dropped columns */ { snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%c$%d", separ, i); separ = ','; } } snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ")"); elog(DEBUG4, "timetravel (%s) update: sql: %s", relname, sql); /* Prepare plan for query */ pplan = SPI_prepare(sql, natts, ctypes); if (pplan == NULL) elog(ERROR, "timetravel (%s): SPI_prepare returned %s", relname, SPI_result_code_string(SPI_result)); /* * Remember that SPI_prepare places plan in current memory context - * so, we have to save plan in Top memory context for later use. */ if (SPI_keepplan(pplan)) elog(ERROR, "timetravel (%s): SPI_keepplan failed", relname); plan->splan = pplan; } /* * Ok, execute prepared plan. */ ret = SPI_execp(plan->splan, cvals, cnulls, 0); if (ret < 0) elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret); /* Tuple to return to upper Executor ... */ if (newtuple) { /* UPDATE */ int chnattrs = 0; int chattrs[MaxAttrNum]; Datum newvals[MaxAttrNum]; char newnulls[MaxAttrNum]; newvals[chnattrs] = newtimeoff; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_time_on]; chnattrs++; newvals[chnattrs] = NOEND_ABSTIME; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_time_off]; chnattrs++; if (argc == MaxAttrNum) { /* set update_user value */ newvals[chnattrs] = newuser; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_upd_user]; chnattrs++; /* clear delete_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = 'n'; chattrs[chnattrs] = attnum[a_del_user]; chnattrs++; /* set insert_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = 'n'; chattrs[chnattrs] = attnum[a_ins_user]; chnattrs++; } /* * Use SPI_modifytuple() here because we are inside SPI environment * but rettuple must be allocated in caller's context. */ rettuple = SPI_modifytuple(rel, newtuple, chnattrs, chattrs, newvals, newnulls); } else /* DELETE case */ rettuple = trigtuple; SPI_finish(); /* don't forget say Bye to SPI mgr */ pfree(relname); return PointerGetDatum(rettuple); }