StringInfo rest_call_with_lock(char *method, char *url, char *params, StringInfo postData, int64 mutex, bool shared, bool allowCancel) { CURL *curl; struct curl_slist *headers = NULL; char *errorbuff; StringInfo response = makeStringInfo(); CURLcode ret; int64 response_code; errorbuff = (char *) palloc0(CURL_ERROR_SIZE); curl = curl_easy_init(); if (curl) { headers = curl_slist_append(headers, "Transfer-Encoding:"); headers = curl_slist_append(headers, "Expect:"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 0L); /* allow connections to be reused */ if (allowCancel) { curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); /* we want progress ... */ curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curl_progress_func); /* to go here so we can detect a ^C within postgres */ } curl_easy_setopt(curl, CURLOPT_USERAGENT, "zombodb for PostgreSQL"); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 0); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_func); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 0); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorbuff); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60 * 60L); /* timeout of 60 minutes */ curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method); curl_easy_setopt(curl, CURLOPT_WRITEDATA, response); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postData ? postData->len : 0); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData ? postData->data : NULL); curl_easy_setopt(curl, CURLOPT_POST, (strcmp(method, "POST") == 0) || (strcmp(method, "GET") != 0 && postData && postData->data) ? 1 : 0); } else { elog(IsTransactionState() ? ERROR : WARNING, "Unable to initialize libcurl"); } // if (mutex != 0) // { // if (shared) DirectFunctionCall1(pg_advisory_lock_shared_int8, Int64GetDatum(mutex)); // else DirectFunctionCall1(pg_advisory_lock_int8, Int64GetDatum(mutex)); // } ret = curl_easy_perform(curl); // if (mutex != 0) // { // if (shared) DirectFunctionCall1(pg_advisory_unlock_shared_int8, Int64GetDatum(mutex)); // else DirectFunctionCall1(pg_advisory_unlock_int8, Int64GetDatum(mutex)); // } if (allowCancel && IsTransactionState() && InterruptPending) { /* we might have detected one in the progress function, so check for sure */ CHECK_FOR_INTERRUPTS(); } if (ret != 0) { /* curl messed up */ elog(IsTransactionState() ? ERROR : WARNING, "libcurl error-code: %s(%d); message: %s; req=-X%s %s ", curl_easy_strerror(ret), ret, errorbuff, method, url); } curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); if (response_code < 200 || (response_code >=300 && response_code != 404)) { text *errorText = DatumGetTextP(DirectFunctionCall2(json_object_field_text, CStringGetTextDatum(response->data), CStringGetTextDatum("error"))); elog(IsTransactionState() ? ERROR : WARNING, "rc=%ld; %s", response_code, errorText != NULL ? TextDatumGetCString(errorText) : response->data); } if (headers) curl_slist_free_all(headers); curl_easy_cleanup(curl); pfree(errorbuff); return response; }
/* * build_function_result_tupdesc_d * * Build a RECORD function's tupledesc from the pg_proc proallargtypes, * proargmodes, and proargnames arrays. This is split out for the * convenience of ProcedureCreate, which needs to be able to compute the * tupledesc before actually creating the function. * * Returns NULL if there are not at least two OUT or INOUT arguments. */ TupleDesc build_function_result_tupdesc_d(Datum proallargtypes, Datum proargmodes, Datum proargnames) { TupleDesc desc; ArrayType *arr; int numargs; Oid *argtypes; char *argmodes; Datum *argnames = NULL; Oid *outargtypes; char **outargnames; int numoutargs; int nargnames; int i; /* Can't have output args if columns are null */ if (proallargtypes == PointerGetDatum(NULL) || proargmodes == PointerGetDatum(NULL)) return NULL; /* * We expect the arrays to be 1-D arrays of the right types; verify that. * For the OID and char arrays, we don't need to use deconstruct_array() * since the array data is just going to look like a C array of values. */ arr = DatumGetArrayTypeP(proallargtypes); /* ensure not toasted */ numargs = ARR_DIMS(arr)[0]; if (ARR_NDIM(arr) != 1 || numargs < 0 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID) elog(ERROR, "proallargtypes is not a 1-D Oid array"); argtypes = (Oid *) ARR_DATA_PTR(arr); arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ if (ARR_NDIM(arr) != 1 || ARR_DIMS(arr)[0] != numargs || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID) elog(ERROR, "proargmodes is not a 1-D char array"); argmodes = (char *) ARR_DATA_PTR(arr); if (proargnames != PointerGetDatum(NULL)) { arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */ if (ARR_NDIM(arr) != 1 || ARR_DIMS(arr)[0] != numargs || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID) elog(ERROR, "proargnames is not a 1-D text array"); deconstruct_array(arr, TEXTOID, -1, false, 'i', &argnames, NULL, &nargnames); Assert(nargnames == numargs); } /* zero elements probably shouldn't happen, but handle it gracefully */ if (numargs <= 0) return NULL; /* extract output-argument types and names */ outargtypes = (Oid *) palloc(numargs * sizeof(Oid)); outargnames = (char **) palloc(numargs * sizeof(char *)); numoutargs = 0; for (i = 0; i < numargs; i++) { char *pname; if (argmodes[i] == PROARGMODE_IN || argmodes[i] == PROARGMODE_VARIADIC) continue; Assert(argmodes[i] == PROARGMODE_OUT || argmodes[i] == PROARGMODE_INOUT || argmodes[i] == PROARGMODE_TABLE); outargtypes[numoutargs] = argtypes[i]; if (argnames) pname = TextDatumGetCString(argnames[i]); else pname = NULL; if (pname == NULL || pname[0] == '\0') { /* Parameter is not named, so gin up a column name */ pname = (char *) palloc(32); snprintf(pname, 32, "column%d", numoutargs + 1); } outargnames[numoutargs] = pname; numoutargs++; } /* * If there is no output argument, or only one, the function does not * return tuples. */ if (numoutargs < 2) return NULL; desc = CreateTemplateTupleDesc(numoutargs, false); for (i = 0; i < numoutargs; i++) { TupleDescInitEntry(desc, i + 1, outargnames[i], outargtypes[i], -1, 0); } return desc; }
/* ---------------------------------------------------------------- * ProcedureCreate * * Note: allParameterTypes, parameterModes, parameterNames, and proconfig * are either arrays of the proper types or NULL. We declare them Datum, * not "ArrayType *", to avoid importing array.h into pg_proc_fn.h. * ---------------------------------------------------------------- */ Oid ProcedureCreate(const char *procedureName, Oid procNamespace, bool replace, bool returnsSet, Oid returnType, Oid languageObjectId, Oid languageValidator, const char *prosrc, const char *probin, bool isAgg, bool isWindowFunc, bool security_definer, bool isStrict, char volatility, oidvector *parameterTypes, Datum allParameterTypes, Datum parameterModes, Datum parameterNames, List *parameterDefaults, Datum proconfig, float4 procost, float4 prorows) { Oid retval; int parameterCount; int allParamCount; Oid *allParams; bool genericInParam = false; bool genericOutParam = false; bool internalInParam = false; bool internalOutParam = false; Oid variadicType = InvalidOid; Oid proowner = GetUserId(); Relation rel; HeapTuple tup; HeapTuple oldtup; bool nulls[Natts_pg_proc]; Datum values[Natts_pg_proc]; bool replaces[Natts_pg_proc]; Oid relid; NameData procname; TupleDesc tupDesc; bool is_update; ObjectAddress myself, referenced; int i; /* * sanity checks */ Assert(PointerIsValid(prosrc)); parameterCount = parameterTypes->dim1; if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), errmsg_plural("functions cannot have more than %d argument", "functions cannot have more than %d arguments", FUNC_MAX_ARGS, FUNC_MAX_ARGS))); /* note: the above is correct, we do NOT count output arguments */ if (allParameterTypes != PointerGetDatum(NULL)) { /* * We expect the array to be a 1-D OID array; verify that. We don't * need to use deconstruct_array() since the array data is just going * to look like a C array of OID values. */ ArrayType *allParamArray = (ArrayType *) DatumGetPointer(allParameterTypes); allParamCount = ARR_DIMS(allParamArray)[0]; if (ARR_NDIM(allParamArray) != 1 || allParamCount <= 0 || ARR_HASNULL(allParamArray) || ARR_ELEMTYPE(allParamArray) != OIDOID) elog(ERROR, "allParameterTypes is not a 1-D Oid array"); allParams = (Oid *) ARR_DATA_PTR(allParamArray); Assert(allParamCount >= parameterCount); /* we assume caller got the contents right */ } else { allParamCount = parameterCount; allParams = parameterTypes->values; } /* * Do not allow polymorphic return type unless at least one input argument * is polymorphic. Also, do not allow return type INTERNAL unless at * least one input argument is INTERNAL. */ for (i = 0; i < parameterCount; i++) { switch (parameterTypes->values[i]) { case ANYARRAYOID: case ANYELEMENTOID: case ANYNONARRAYOID: case ANYENUMOID: genericInParam = true; break; case INTERNALOID: internalInParam = true; break; } } if (allParameterTypes != PointerGetDatum(NULL)) { for (i = 0; i < allParamCount; i++) { /* * We don't bother to distinguish input and output params here, so * if there is, say, just an input INTERNAL param then we will * still set internalOutParam. This is OK since we don't really * care. */ switch (allParams[i]) { case ANYARRAYOID: case ANYELEMENTOID: case ANYNONARRAYOID: case ANYENUMOID: genericOutParam = true; break; case INTERNALOID: internalOutParam = true; break; } } } if ((IsPolymorphicType(returnType) || genericOutParam) && !genericInParam) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine result data type"), errdetail("A function returning a polymorphic type must have at least one polymorphic argument."))); if ((returnType == INTERNALOID || internalOutParam) && !internalInParam) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("unsafe use of pseudo-type \"internal\""), errdetail("A function returning \"internal\" must have at least one \"internal\" argument."))); /* * don't allow functions of complex types that have the same name as * existing attributes of the type */ if (parameterCount == 1 && OidIsValid(parameterTypes->values[0]) && (relid = typeidTypeRelid(parameterTypes->values[0])) != InvalidOid && get_attnum(relid, procedureName) != InvalidAttrNumber) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN), errmsg("\"%s\" is already an attribute of type %s", procedureName, format_type_be(parameterTypes->values[0])))); if (parameterModes != PointerGetDatum(NULL)) { /* * We expect the array to be a 1-D CHAR array; verify that. We don't * need to use deconstruct_array() since the array data is just going * to look like a C array of char values. */ ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes); char *modes; if (ARR_NDIM(modesArray) != 1 || ARR_DIMS(modesArray)[0] != allParamCount || ARR_HASNULL(modesArray) || ARR_ELEMTYPE(modesArray) != CHAROID) elog(ERROR, "parameterModes is not a 1-D char array"); modes = (char *) ARR_DATA_PTR(modesArray); /* * Only the last input parameter can be variadic; if it is, save its * element type. Errors here are just elog since caller should have * checked this already. */ for (i = 0; i < allParamCount; i++) { switch (modes[i]) { case PROARGMODE_IN: case PROARGMODE_INOUT: if (OidIsValid(variadicType)) elog(ERROR, "variadic parameter must be last"); break; case PROARGMODE_OUT: case PROARGMODE_TABLE: /* okay */ break; case PROARGMODE_VARIADIC: if (OidIsValid(variadicType)) elog(ERROR, "variadic parameter must be last"); switch (allParams[i]) { case ANYOID: variadicType = ANYOID; break; case ANYARRAYOID: variadicType = ANYELEMENTOID; break; default: variadicType = get_element_type(allParams[i]); if (!OidIsValid(variadicType)) elog(ERROR, "variadic parameter is not an array"); break; } break; default: elog(ERROR, "invalid parameter mode '%c'", modes[i]); break; } } } /* * All seems OK; prepare the data to be inserted into pg_proc. */ for (i = 0; i < Natts_pg_proc; ++i) { nulls[i] = false; values[i] = (Datum) 0; replaces[i] = true; } namestrcpy(&procname, procedureName); values[Anum_pg_proc_proname - 1] = NameGetDatum(&procname); values[Anum_pg_proc_pronamespace - 1] = ObjectIdGetDatum(procNamespace); values[Anum_pg_proc_proowner - 1] = ObjectIdGetDatum(proowner); values[Anum_pg_proc_prolang - 1] = ObjectIdGetDatum(languageObjectId); values[Anum_pg_proc_procost - 1] = Float4GetDatum(procost); values[Anum_pg_proc_prorows - 1] = Float4GetDatum(prorows); values[Anum_pg_proc_provariadic - 1] = ObjectIdGetDatum(variadicType); values[Anum_pg_proc_proisagg - 1] = BoolGetDatum(isAgg); values[Anum_pg_proc_proiswindow - 1] = BoolGetDatum(isWindowFunc); values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer); values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict); values[Anum_pg_proc_proretset - 1] = BoolGetDatum(returnsSet); values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility); values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount); values[Anum_pg_proc_pronargdefaults - 1] = UInt16GetDatum(list_length(parameterDefaults)); values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType); values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes); if (allParameterTypes != PointerGetDatum(NULL)) values[Anum_pg_proc_proallargtypes - 1] = allParameterTypes; else nulls[Anum_pg_proc_proallargtypes - 1] = true; if (parameterModes != PointerGetDatum(NULL)) values[Anum_pg_proc_proargmodes - 1] = parameterModes; else nulls[Anum_pg_proc_proargmodes - 1] = true; if (parameterNames != PointerGetDatum(NULL)) values[Anum_pg_proc_proargnames - 1] = parameterNames; else nulls[Anum_pg_proc_proargnames - 1] = true; if (parameterDefaults != NIL) values[Anum_pg_proc_proargdefaults - 1] = CStringGetTextDatum(nodeToString(parameterDefaults)); else nulls[Anum_pg_proc_proargdefaults - 1] = true; values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc); if (probin) values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin); else nulls[Anum_pg_proc_probin - 1] = true; if (proconfig != PointerGetDatum(NULL)) values[Anum_pg_proc_proconfig - 1] = proconfig; else nulls[Anum_pg_proc_proconfig - 1] = true; /* start out with empty permissions */ nulls[Anum_pg_proc_proacl - 1] = true; rel = heap_open(ProcedureRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); /* Check for pre-existing definition */ oldtup = SearchSysCache(PROCNAMEARGSNSP, PointerGetDatum(procedureName), PointerGetDatum(parameterTypes), ObjectIdGetDatum(procNamespace), 0); if (HeapTupleIsValid(oldtup)) { /* There is one; okay to replace it? */ Form_pg_proc oldproc = (Form_pg_proc) GETSTRUCT(oldtup); if (!replace) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_FUNCTION), errmsg("function \"%s\" already exists with same argument types", procedureName))); if (!pg_proc_ownercheck(HeapTupleGetOid(oldtup), proowner)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, procedureName); /* * Not okay to change the return type of the existing proc, since * existing rules, views, etc may depend on the return type. */ if (returnType != oldproc->prorettype || returnsSet != oldproc->proretset) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot change return type of existing function"), errhint("Use DROP FUNCTION first."))); /* * If it returns RECORD, check for possible change of record type * implied by OUT parameters */ if (returnType == RECORDOID) { TupleDesc olddesc; TupleDesc newdesc; olddesc = build_function_result_tupdesc_t(oldtup); newdesc = build_function_result_tupdesc_d(allParameterTypes, parameterModes, parameterNames); if (olddesc == NULL && newdesc == NULL) /* ok, both are runtime-defined RECORDs */ ; else if (olddesc == NULL || newdesc == NULL || !equalTupleDescs(olddesc, newdesc)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot change return type of existing function"), errdetail("Row type defined by OUT parameters is different."), errhint("Use DROP FUNCTION first."))); } /* * If there are existing defaults, check compatibility: redefinition * must not remove any defaults nor change their types. (Removing a * default might cause a function to fail to satisfy an existing call. * Changing type would only be possible if the associated parameter is * polymorphic, and in such cases a change of default type might alter * the resolved output type of existing calls.) */ if (oldproc->pronargdefaults != 0) { Datum proargdefaults; bool isnull; List *oldDefaults; ListCell *oldlc; ListCell *newlc; if (list_length(parameterDefaults) < oldproc->pronargdefaults) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot remove parameter defaults from existing function"), errhint("Use DROP FUNCTION first."))); proargdefaults = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup, Anum_pg_proc_proargdefaults, &isnull); Assert(!isnull); oldDefaults = (List *) stringToNode(TextDatumGetCString(proargdefaults)); Assert(IsA(oldDefaults, List)); Assert(list_length(oldDefaults) == oldproc->pronargdefaults); /* new list can have more defaults than old, advance over 'em */ newlc = list_head(parameterDefaults); for (i = list_length(parameterDefaults) - oldproc->pronargdefaults; i > 0; i--) newlc = lnext(newlc); foreach(oldlc, oldDefaults) { Node *oldDef = (Node *) lfirst(oldlc); Node *newDef = (Node *) lfirst(newlc); if (exprType(oldDef) != exprType(newDef)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot change data type of existing parameter default value"), errhint("Use DROP FUNCTION first."))); newlc = lnext(newlc); } }
/* * Helper function for the various SQL callable logical decoding functions. */ static Datum pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool binary) { Name name; XLogRecPtr upto_lsn; int32 upto_nchanges; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; MemoryContext per_query_ctx; MemoryContext oldcontext; XLogRecPtr end_of_wal; XLogRecPtr startptr; LogicalDecodingContext *ctx; ResourceOwner old_resowner = CurrentResourceOwner; ArrayType *arr; Size ndim; List *options = NIL; DecodingOutputState *p; check_permissions(); CheckLogicalDecodingRequirements(); if (PG_ARGISNULL(0)) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("slot name must not be null"))); name = PG_GETARG_NAME(0); if (PG_ARGISNULL(1)) upto_lsn = InvalidXLogRecPtr; else upto_lsn = PG_GETARG_LSN(1); if (PG_ARGISNULL(2)) upto_nchanges = InvalidXLogRecPtr; else upto_nchanges = PG_GETARG_INT32(2); if (PG_ARGISNULL(3)) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("options array must not be null"))); arr = PG_GETARG_ARRAYTYPE_P(3); /* 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"))); /* state to write output to */ p = palloc0(sizeof(DecodingOutputState)); p->binary_output = binary; /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &p->tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); /* Deconstruct options array */ ndim = ARR_NDIM(arr); if (ndim > 1) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("array must be one-dimensional"))); } else if (array_contains_nulls(arr)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("array must not contain nulls"))); } else if (ndim == 1) { int nelems; Datum *datum_opts; int i; Assert(ARR_ELEMTYPE(arr) == TEXTOID); deconstruct_array(arr, TEXTOID, -1, false, 'i', &datum_opts, NULL, &nelems); if (nelems % 2 != 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("array must have even number of elements"))); for (i = 0; i < nelems; i += 2) { char *name = TextDatumGetCString(datum_opts[i]); char *opt = TextDatumGetCString(datum_opts[i + 1]); options = lappend(options, makeDefElem(name, (Node *) makeString(opt), -1)); } } p->tupstore = tuplestore_begin_heap(true, false, work_mem); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = p->tupstore; rsinfo->setDesc = p->tupdesc; /* compute the current end-of-wal */ if (!RecoveryInProgress()) end_of_wal = GetFlushRecPtr(); else end_of_wal = GetXLogReplayRecPtr(NULL); ReplicationSlotAcquire(NameStr(*name)); PG_TRY(); { /* restart at slot's confirmed_flush */ ctx = CreateDecodingContext(InvalidXLogRecPtr, options, logical_read_local_xlog_page, LogicalOutputPrepareWrite, LogicalOutputWrite); MemoryContextSwitchTo(oldcontext); /* * Check whether the output plugin writes textual output if that's * what we need. */ if (!binary && ctx->options.output_type !=OUTPUT_PLUGIN_TEXTUAL_OUTPUT) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("logical decoding output plugin \"%s\" produces binary output, but function \"%s\" expects textual data", NameStr(MyReplicationSlot->data.plugin), format_procedure(fcinfo->flinfo->fn_oid)))); ctx->output_writer_private = p; /* * Decoding of WAL must start at restart_lsn so that the entirety of * xacts that committed after the slot's confirmed_flush can be * accumulated into reorder buffers. */ startptr = MyReplicationSlot->data.restart_lsn; CurrentResourceOwner = ResourceOwnerCreate(CurrentResourceOwner, "logical decoding"); /* invalidate non-timetravel entries */ InvalidateSystemCaches(); while ((startptr != InvalidXLogRecPtr && startptr < end_of_wal) || (ctx->reader->EndRecPtr != InvalidXLogRecPtr && ctx->reader->EndRecPtr < end_of_wal)) { XLogRecord *record; char *errm = NULL; record = XLogReadRecord(ctx->reader, startptr, &errm); if (errm) elog(ERROR, "%s", errm); /* * Now that we've set up the xlog reader state, subsequent calls * pass InvalidXLogRecPtr to say "continue from last record" */ startptr = InvalidXLogRecPtr; /* * The {begin_txn,change,commit_txn}_wrapper callbacks above will * store the description into our tuplestore. */ if (record != NULL) LogicalDecodingProcessRecord(ctx, ctx->reader); /* check limits */ if (upto_lsn != InvalidXLogRecPtr && upto_lsn <= ctx->reader->EndRecPtr) break; if (upto_nchanges != 0 && upto_nchanges <= p->returned_rows) break; CHECK_FOR_INTERRUPTS(); } tuplestore_donestoring(tupstore); CurrentResourceOwner = old_resowner; /* * Next time, start where we left off. (Hunting things, the family * business..) */ if (ctx->reader->EndRecPtr != InvalidXLogRecPtr && confirm) { LogicalConfirmReceivedLocation(ctx->reader->EndRecPtr); /* * If only the confirmed_flush_lsn has changed the slot won't get * marked as dirty by the above. Callers on the walsender interface * are expected to keep track of their own progress and don't need * it written out. But SQL-interface users cannot specify their own * start positions and it's harder for them to keep track of their * progress, so we should make more of an effort to save it for them. * * Dirty the slot so it's written out at the next checkpoint. We'll * still lose its position on crash, as documented, but it's better * than always losing the position even on clean restart. */ ReplicationSlotMarkDirty(); } /* free context, call shutdown callback */ FreeDecodingContext(ctx); ReplicationSlotRelease(); InvalidateSystemCaches(); } PG_CATCH(); { /* clear all timetravel entries */ InvalidateSystemCaches(); PG_RE_THROW(); } PG_END_TRY(); return (Datum) 0; }
/* * SQL function jsonb_object(text[], text[]) * * take separate name and value arrays of text to construct a jsonb object * pairwise. */ Datum jsonb_object_two_arg(PG_FUNCTION_ARGS) { ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(0); ArrayType *val_array = PG_GETARG_ARRAYTYPE_P(1); int nkdims = ARR_NDIM(key_array); int nvdims = ARR_NDIM(val_array); Datum *key_datums, *val_datums; bool *key_nulls, *val_nulls; int key_count, val_count, i; JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); if (nkdims > 1 || nkdims != nvdims) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("wrong number of array subscripts"))); if (nkdims == 0) PG_RETURN_DATUM(CStringGetTextDatum("{}")); deconstruct_array(key_array, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, &key_count); deconstruct_array(val_array, TEXTOID, -1, false, 'i', &val_datums, &val_nulls, &val_count); if (key_count != val_count) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("mismatched array dimensions"))); for (i = 0; i < key_count; ++i) { JsonbValue v; char *str; int len; if (key_nulls[i]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for object key"))); str = TextDatumGetCString(key_datums[i]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; (void) pushJsonbValue(&result.parseState, WJB_KEY, &v); if (val_nulls[i]) { v.type = jbvNull; } else { str = TextDatumGetCString(val_datums[i]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; } (void) pushJsonbValue(&result.parseState, WJB_VALUE, &v); } result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); pfree(key_datums); pfree(key_nulls); pfree(val_datums); pfree(val_nulls); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); }
/* * SQL function jsonb_object(text[]) * * take a one or two dimensional array of text as name value pairs * for a jsonb object. * */ Datum jsonb_object(PG_FUNCTION_ARGS) { ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); int ndims = ARR_NDIM(in_array); Datum *in_datums; bool *in_nulls; int in_count, count, i; JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); switch (ndims) { case 0: goto close_object; break; case 1: if ((ARR_DIMS(in_array)[0]) % 2) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("array must have even number of elements"))); break; case 2: if ((ARR_DIMS(in_array)[1]) != 2) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("array must have two columns"))); break; default: ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("wrong number of array subscripts"))); } deconstruct_array(in_array, TEXTOID, -1, false, 'i', &in_datums, &in_nulls, &in_count); count = in_count / 2; for (i = 0; i < count; ++i) { JsonbValue v; char *str; int len; if (in_nulls[i * 2]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for object key"))); str = TextDatumGetCString(in_datums[i * 2]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; (void) pushJsonbValue(&result.parseState, WJB_KEY, &v); if (in_nulls[i * 2 + 1]) { v.type = jbvNull; } else { str = TextDatumGetCString(in_datums[i * 2 + 1]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; } (void) pushJsonbValue(&result.parseState, WJB_VALUE, &v); } pfree(in_datums); pfree(in_nulls); close_object: result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); }
/* * Look to see if we have template information for the given language name. */ static PLTemplate * find_language_template(const char *languageName) { PLTemplate *result; Relation rel; SysScanDesc scan; ScanKeyData key; HeapTuple tup; rel = heap_open(PLTemplateRelationId, AccessShareLock); ScanKeyInit(&key, Anum_pg_pltemplate_tmplname, BTEqualStrategyNumber, F_NAMEEQ, NameGetDatum(languageName)); scan = systable_beginscan(rel, PLTemplateNameIndexId, true, SnapshotNow, 1, &key); tup = systable_getnext(scan); if (HeapTupleIsValid(tup)) { Form_pg_pltemplate tmpl = (Form_pg_pltemplate) GETSTRUCT(tup); Datum datum; bool isnull; result = (PLTemplate *) palloc0(sizeof(PLTemplate)); result->tmpltrusted = tmpl->tmpltrusted; result->tmpldbacreate = tmpl->tmpldbacreate; /* Remaining fields are variable-width so we need heap_getattr */ datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler, RelationGetDescr(rel), &isnull); if (!isnull) result->tmplhandler = TextDatumGetCString(datum); datum = heap_getattr(tup, Anum_pg_pltemplate_tmplinline, RelationGetDescr(rel), &isnull); if (!isnull) result->tmplinline = TextDatumGetCString(datum); datum = heap_getattr(tup, Anum_pg_pltemplate_tmplvalidator, RelationGetDescr(rel), &isnull); if (!isnull) result->tmplvalidator = TextDatumGetCString(datum); datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary, RelationGetDescr(rel), &isnull); if (!isnull) result->tmpllibrary = TextDatumGetCString(datum); /* Ignore template if handler or library info is missing */ if (!result->tmplhandler || !result->tmpllibrary) result = NULL; } else result = NULL; systable_endscan(scan); heap_close(rel, AccessShareLock); return result; }
/* * Fetch the subscription from the syscache. */ Subscription * GetSubscription(Oid subid, bool missing_ok) { HeapTuple tup; Subscription *sub; Form_pg_subscription subform; Datum datum; bool isnull; tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid)); if (!HeapTupleIsValid(tup)) { if (missing_ok) return NULL; elog(ERROR, "cache lookup failed for subscription %u", subid); } subform = (Form_pg_subscription) GETSTRUCT(tup); sub = (Subscription *) palloc(sizeof(Subscription)); sub->oid = subid; sub->dbid = subform->subdbid; sub->name = pstrdup(NameStr(subform->subname)); sub->owner = subform->subowner; sub->enabled = subform->subenabled; /* Get conninfo */ datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subconninfo, &isnull); Assert(!isnull); sub->conninfo = TextDatumGetCString(datum); /* Get slotname */ datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subslotname, &isnull); if (!isnull) sub->slotname = pstrdup(NameStr(*DatumGetName(datum))); else sub->slotname = NULL; /* Get synccommit */ datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subsynccommit, &isnull); Assert(!isnull); sub->synccommit = TextDatumGetCString(datum); /* Get publications */ datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subpublications, &isnull); Assert(!isnull); sub->publications = textarray_to_stringlist(DatumGetArrayTypeP(datum)); ReleaseSysCache(tup); return sub; }
// ----------------------------------------------------------------------------- // Rebuild filespace persistent table 'gp_persistent_filespace_node' // ----------------------------------------------------------------------------- void PersistentFilespace_AddCreated( Oid filespaceOid, /* The filespace OID to be added. */ bool flushToXLog) /* When true, the XLOG record for this change will be flushed to disk. */ { WRITE_PERSISTENT_STATE_ORDERED_LOCK_DECLARE; PersistentFileSysObjName fsObjName; ItemPointerData persistentTid; int64 persistentSerialNum; FilespaceDirEntry filespaceDirEntry; /*if (Persistent_BeforePersistenceWork()) { if (Debug_persistent_print) elog(Persistent_DebugPrintLevel(), "Skipping persistent filespace %u because we are before persistence work", filespaceOid); return; // The initdb process will load the persistent table once we out of bootstrap mode. }*/ PersistentFilespace_VerifyInitScan(); PersistentFileSysObjName_SetFilespaceDir(&fsObjName,filespaceOid,is_filespace_shared); WRITE_PERSISTENT_STATE_ORDERED_LOCK; filespaceDirEntry = PersistentFilespace_CreateDirUnderLock(filespaceOid); if (filespaceDirEntry == NULL) { /* If out of shared memory, no need to promote to PANIC. */ WRITE_PERSISTENT_STATE_ORDERED_UNLOCK; ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Out of shared-memory for persistent filespaces"), errhint("You may need to increase the gp_max_filespaces value"), errOmitLocation(true))); } // if it is a new generated one, we need to set info from pg_filespace_entry if(filespaceDirEntry->persistentSerialNum==0 || strlen(filespaceDirEntry->locationBlankPadded1)==0) { Relation pg_fs_entry_rel; HeapScanDesc scandesc; HeapTuple tuple; ScanKeyData entry[1]; bool isNull; Datum locDatum; char *loc; /* Lookup the information for the current pg_filespace_entry */ pg_fs_entry_rel = heap_open(FileSpaceEntryRelationId, AccessShareLock); ScanKeyInit(&entry[0], Anum_pg_filespace_entry_fsefsoid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(filespaceOid)); scandesc = heap_beginscan(pg_fs_entry_rel, SnapshotNow, 1, entry); tuple = heap_getnext(scandesc, ForwardScanDirection); /* We assume that there can be at most one matching tuple */ if (!HeapTupleIsValid(tuple)) { elog(ERROR, "filespace %u could not be found in pg_filespace_entry", filespaceOid); } locDatum = heap_getattr(tuple, Anum_pg_filespace_entry_fselocation, pg_fs_entry_rel->rd_att, &isNull); loc = TextDatumGetCString(locDatum); //convert location with blank padded memset(filespaceDirEntry->locationBlankPadded1, ' ', FilespaceLocationBlankPaddedWithNullTermLen); filespaceDirEntry->locationBlankPadded1[FilespaceLocationBlankPaddedWithNullTermLen-1]='\0'; memcpy(filespaceDirEntry->locationBlankPadded1, loc, strlen(loc)); if(isNull) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("internal error: filespace '%u' has no name defined", filespaceOid))); heap_endscan(scandesc); heap_close(pg_fs_entry_rel, AccessShareLock); } filespaceDirEntry->state = PersistentFileSysState_Created; PersistentFilespace_AddTuple( filespaceDirEntry, /* createMirrorDataLossTrackingSessionNum */ 0, /* reserved */ 0, /* parentXid */ InvalidTransactionId, flushToXLog); persistentTid = filespaceDirEntry->persistentTid; persistentSerialNum = filespaceDirEntry->persistentSerialNum; WRITE_PERSISTENT_STATE_ORDERED_UNLOCK; if (Debug_persistent_print) elog(Persistent_DebugPrintLevel(), "Persistent filespace directory: Add '%s' in state 'Created', serial number " INT64_FORMAT " at TID '%s' ", PersistentFileSysObjName_ObjectName(&fsObjName), persistentSerialNum, ItemPointerToString(&persistentTid)); }
/* * get_func_result_name * * If the function has exactly one output parameter, and that parameter * is named, return the name (as a palloc'd string). Else return NULL. * * This is used to determine the default output column name for functions * returning scalar types. */ char * get_func_result_name(Oid functionId) { char *result; HeapTuple procTuple; Datum proargmodes; Datum proargnames; bool isnull; ArrayType *arr; int numargs; char *argmodes; Datum *argnames; int numoutargs; int nargnames; int i; /* First fetch the function's pg_proc row */ procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId)); if (!HeapTupleIsValid(procTuple)) elog(ERROR, "cache lookup failed for function %u", functionId); /* If there are no named OUT parameters, return NULL */ if (heap_attisnull(procTuple, Anum_pg_proc_proargmodes) || heap_attisnull(procTuple, Anum_pg_proc_proargnames)) result = NULL; else { /* Get the data out of the tuple */ proargmodes = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proargmodes, &isnull); Assert(!isnull); proargnames = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proargnames, &isnull); Assert(!isnull); /* * We expect the arrays to be 1-D arrays of the right types; verify * that. For the char array, we don't need to use deconstruct_array() * since the array data is just going to look like a C array of * values. */ arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ numargs = ARR_DIMS(arr)[0]; if (ARR_NDIM(arr) != 1 || numargs < 0 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID) elog(ERROR, "proargmodes is not a 1-D char array"); argmodes = (char *) ARR_DATA_PTR(arr); arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */ if (ARR_NDIM(arr) != 1 || ARR_DIMS(arr)[0] != numargs || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID) elog(ERROR, "proargnames is not a 1-D text array"); deconstruct_array(arr, TEXTOID, -1, false, 'i', &argnames, NULL, &nargnames); Assert(nargnames == numargs); /* scan for output argument(s) */ result = NULL; numoutargs = 0; for (i = 0; i < numargs; i++) { if (argmodes[i] == PROARGMODE_IN || argmodes[i] == PROARGMODE_VARIADIC) continue; Assert(argmodes[i] == PROARGMODE_OUT || argmodes[i] == PROARGMODE_INOUT || argmodes[i] == PROARGMODE_TABLE); if (++numoutargs > 1) { /* multiple out args, so forget it */ result = NULL; break; } result = TextDatumGetCString(argnames[i]); if (result == NULL || result[0] == '\0') { /* Parameter is not named, so forget it */ result = NULL; break; } } } ReleaseSysCache(procTuple); return result; }
/* * Helper function for pgp_armor. Converts arrays of keys and values into * plain C arrays, and checks that they don't contain invalid characters. */ static int parse_key_value_arrays(ArrayType *key_array, ArrayType *val_array, char ***p_keys, char ***p_values) { int nkdims = ARR_NDIM(key_array); int nvdims = ARR_NDIM(val_array); char **keys, **values; Datum *key_datums, *val_datums; bool *key_nulls, *val_nulls; int key_count, val_count; int i; if (nkdims > 1 || nkdims != nvdims) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("wrong number of array subscripts"))); if (nkdims == 0) return 0; deconstruct_array(key_array, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, &key_count); deconstruct_array(val_array, TEXTOID, -1, false, 'i', &val_datums, &val_nulls, &val_count); if (key_count != val_count) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("mismatched array dimensions"))); keys = (char **) palloc(sizeof(char *) * key_count); values = (char **) palloc(sizeof(char *) * val_count); for (i = 0; i < key_count; i++) { char *v; /* Check that the key doesn't contain anything funny */ if (key_nulls[i]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for header key"))); v = TextDatumGetCString(key_datums[i]); if (!string_is_ascii(v)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("header key must not contain non-ASCII characters"))); if (strstr(v, ": ")) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("header key must not contain \": \""))); if (strchr(v, '\n')) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("header key must not contain newlines"))); keys[i] = v; /* And the same for the value */ if (val_nulls[i]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for header value"))); v = TextDatumGetCString(val_datums[i]); if (!string_is_ascii(v)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("header value must not contain non-ASCII characters"))); if (strchr(v, '\n')) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("header value must not contain newlines"))); values[i] = v; } *p_keys = keys; *p_values = values; return key_count; }
/* * FetchTableCommon executes common logic that wraps around the actual data * fetching function. This common logic includes ensuring that only one process * tries to fetch this table at any given time, and that data fetch operations * are retried in case of node failures. */ static void FetchTableCommon(text *tableNameText, uint64 remoteTableSize, ArrayType *nodeNameObject, ArrayType *nodePortObject, bool (*FetchTableFunction)(const char *, uint32, const char *)) { uint64 shardId = INVALID_SHARD_ID; Oid relationId = InvalidOid; List *relationNameList = NIL; RangeVar *relation = NULL; uint32 nodeIndex = 0; bool tableFetched = false; char *tableName = text_to_cstring(tableNameText); Datum *nodeNameArray = DeconstructArrayObject(nodeNameObject); Datum *nodePortArray = DeconstructArrayObject(nodePortObject); int32 nodeNameCount = ArrayObjectCount(nodeNameObject); int32 nodePortCount = ArrayObjectCount(nodePortObject); /* we should have the same number of node names and port numbers */ if (nodeNameCount != nodePortCount) { ereport(ERROR, (errmsg("node name array size: %d and node port array size: %d" " do not match", nodeNameCount, nodePortCount))); } /* * We lock on the shardId, but do not unlock. When the function returns, and * the transaction for this function commits, this lock will automatically * be released. This ensures that concurrent caching commands will see the * newly created table when they acquire the lock (in read committed mode). */ shardId = ExtractShardId(tableName); LockShardResource(shardId, AccessExclusiveLock); relationNameList = textToQualifiedNameList(tableNameText); relation = makeRangeVarFromNameList(relationNameList); relationId = RangeVarGetRelid(relation, NoLock, true); /* check if we already fetched the table */ if (relationId != InvalidOid) { uint64 localTableSize = 0; if (!ExpireCachedShards) { return; } /* * Check if the cached shard has the same size on disk as it has as on * the placement (is up to date). * * Note 1: performing updates or deletes on the original shard leads to * inconsistent sizes between different databases in which case the data * would be fetched every time, or worse, the placement would get into * a deadlock when it tries to fetch from itself while holding the lock. * Therefore, this option is disabled by default. * * Note 2: when appending data to a shard, the size on disk only * increases when a new page is added (the next 8kB block). */ localTableSize = LocalTableSize(relationId); if (remoteTableSize > localTableSize) { /* table is not up to date, drop the table */ ObjectAddress tableObject = { InvalidOid, InvalidOid, 0 }; tableObject.classId = RelationRelationId; tableObject.objectId = relationId; tableObject.objectSubId = 0; performDeletion(&tableObject, DROP_RESTRICT, PERFORM_DELETION_INTERNAL); } else { /* table is up to date */ return; } } /* loop until we fetch the table or try all nodes */ while (!tableFetched && (nodeIndex < nodeNameCount)) { Datum nodeNameDatum = nodeNameArray[nodeIndex]; Datum nodePortDatum = nodePortArray[nodeIndex]; char *nodeName = TextDatumGetCString(nodeNameDatum); uint32 nodePort = DatumGetUInt32(nodePortDatum); tableFetched = (*FetchTableFunction)(nodeName, nodePort, tableName); nodeIndex++; } /* error out if we tried all nodes and could not fetch the table */ if (!tableFetched) { ereport(ERROR, (errmsg("could not fetch relation: \"%s\"", tableName))); } }
char *lookup_field_mapping(MemoryContext cxt, Oid tableRelId, char *fieldname) { char *definition = NULL; StringInfo query; SPI_connect(); query = makeStringInfo(); appendStringInfo(query, "select definition from zdb_mappings where table_name = %d::regclass and field_name = %s;", tableRelId, TextDatumGetCString(DirectFunctionCall1(quote_literal, CStringGetTextDatum(fieldname)))); if (SPI_execute(query->data, true, 2) != SPI_OK_SELECT) elog(ERROR, "Problem looking up analysis thing with query: %s", query->data); if (SPI_processed > 1) { elog(ERROR, "Too many mappings found"); } else if (SPI_processed == 1) { char *json = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1); Size len = strlen(json); definition = (char *) MemoryContextAllocZero(cxt, (Size) len + 1); memcpy(definition, json, len); } SPI_finish(); return definition; }
bool type_is_domain(char *type_name, Oid *base_type) { bool rc; StringInfo query; SPI_connect(); query = makeStringInfo(); appendStringInfo(query, "SELECT typtype = 'd', typbasetype FROM pg_type WHERE typname = %s", TextDatumGetCString(DirectFunctionCall1(quote_literal, CStringGetTextDatum(type_name)))); if (SPI_execute(query->data, true, 1) != SPI_OK_SELECT) elog(ERROR, "Problem determing if %s is a domain with query: %s", type_name, query->data); if (SPI_processed == 0) { rc = false; } else { bool isnull; Datum d; d = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull); rc = isnull || DatumGetBool(d); d = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull); *base_type = isnull ? InvalidOid : DatumGetObjectId(d); } SPI_finish(); return rc; }
/* * get_func_arg_info * * Fetch info about the argument types, names, and IN/OUT modes from the * pg_proc tuple. Return value is the total number of arguments. * Other results are palloc'd. *p_argtypes is always filled in, but * *p_argnames and *p_argmodes will be set NULL in the default cases * (no names, and all IN arguments, respectively). * * Note that this function simply fetches what is in the pg_proc tuple; * it doesn't do any interpretation of polymorphic types. */ int get_func_arg_info(HeapTuple procTup, Oid **p_argtypes, char ***p_argnames, char **p_argmodes) { Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); Datum proallargtypes; Datum proargmodes; Datum proargnames; bool isNull; ArrayType *arr; int numargs; Datum *elems; int nelems; int i; /* First discover the total number of parameters and get their types */ proallargtypes = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proallargtypes, &isNull); if (!isNull) { /* * We expect the arrays to be 1-D arrays of the right types; verify * that. For the OID and char arrays, we don't need to use * deconstruct_array() since the array data is just going to look like * a C array of values. */ arr = DatumGetArrayTypeP(proallargtypes); /* ensure not toasted */ numargs = ARR_DIMS(arr)[0]; if (ARR_NDIM(arr) != 1 || numargs < 0 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID) elog(ERROR, "proallargtypes is not a 1-D Oid array"); Assert(numargs >= procStruct->pronargs); *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid)); memcpy(*p_argtypes, ARR_DATA_PTR(arr), numargs * sizeof(Oid)); } else { /* If no proallargtypes, use proargtypes */ numargs = procStruct->proargtypes.dim1; Assert(numargs == procStruct->pronargs); *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid)); memcpy(*p_argtypes, procStruct->proargtypes.values, numargs * sizeof(Oid)); } /* Get argument names, if available */ proargnames = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames, &isNull); if (isNull) *p_argnames = NULL; else { deconstruct_array(DatumGetArrayTypeP(proargnames), TEXTOID, -1, false, 'i', &elems, NULL, &nelems); if (nelems != numargs) /* should not happen */ elog(ERROR, "proargnames must have the same number of elements as the function has arguments"); *p_argnames = (char **) palloc(sizeof(char *) * numargs); for (i = 0; i < numargs; i++) (*p_argnames)[i] = TextDatumGetCString(elems[i]); } /* Get argument modes, if available */ proargmodes = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargmodes, &isNull); if (isNull) *p_argmodes = NULL; else { arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ if (ARR_NDIM(arr) != 1 || ARR_DIMS(arr)[0] != numargs || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID) elog(ERROR, "proargmodes is not a 1-D char array"); *p_argmodes = (char *) palloc(numargs * sizeof(char)); memcpy(*p_argmodes, ARR_DATA_PTR(arr), numargs * sizeof(char)); } return numargs; }
/* * Helper function for the various SQL callable logical decoding functions. */ static Datum pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool binary) { Name name = PG_GETARG_NAME(0); XLogRecPtr upto_lsn; int32 upto_nchanges; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; MemoryContext per_query_ctx; MemoryContext oldcontext; XLogRecPtr end_of_wal; XLogRecPtr startptr; LogicalDecodingContext *ctx; ResourceOwner old_resowner = CurrentResourceOwner; ArrayType *arr; Size ndim; List *options = NIL; DecodingOutputState *p; if (PG_ARGISNULL(1)) upto_lsn = InvalidXLogRecPtr; else upto_lsn = PG_GETARG_LSN(1); if (PG_ARGISNULL(2)) upto_nchanges = InvalidXLogRecPtr; else upto_nchanges = PG_GETARG_INT32(2); /* 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"))); /* state to write output to */ p = palloc0(sizeof(DecodingOutputState)); p->binary_output = binary; /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &p->tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); check_permissions(); CheckLogicalDecodingRequirements(); arr = PG_GETARG_ARRAYTYPE_P(3); ndim = ARR_NDIM(arr); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); if (ndim > 1) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("array must be one-dimensional"))); } else if (array_contains_nulls(arr)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("array must not contain nulls"))); } else if (ndim == 1) { int nelems; Datum *datum_opts; int i; Assert(ARR_ELEMTYPE(arr) == TEXTOID); deconstruct_array(arr, TEXTOID, -1, false, 'i', &datum_opts, NULL, &nelems); if (nelems % 2 != 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("array must have even number of elements"))); for (i = 0; i < nelems; i += 2) { char *name = TextDatumGetCString(datum_opts[i]); char *opt = TextDatumGetCString(datum_opts[i + 1]); options = lappend(options, makeDefElem(name, (Node *) makeString(opt))); } } p->tupstore = tuplestore_begin_heap(true, false, work_mem); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = p->tupstore; rsinfo->setDesc = p->tupdesc; /* compute the current end-of-wal */ if (!RecoveryInProgress()) end_of_wal = GetFlushRecPtr(); else end_of_wal = GetXLogReplayRecPtr(NULL); CheckLogicalDecodingRequirements(); ReplicationSlotAcquire(NameStr(*name)); PG_TRY(); { ctx = CreateDecodingContext(InvalidXLogRecPtr, options, logical_read_local_xlog_page, LogicalOutputPrepareWrite, LogicalOutputWrite); MemoryContextSwitchTo(oldcontext); /* * Check whether the output pluggin writes textual output if that's * what we need. */ if (!binary && ctx->options.output_type != OUTPUT_PLUGIN_TEXTUAL_OUTPUT) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("output plugin cannot produce binary output"))); ctx->output_writer_private = p; startptr = MyReplicationSlot->data.restart_lsn; CurrentResourceOwner = ResourceOwnerCreate(CurrentResourceOwner, "logical decoding"); /* invalidate non-timetravel entries */ InvalidateSystemCaches(); while ((startptr != InvalidXLogRecPtr && startptr < end_of_wal) || (ctx->reader->EndRecPtr && ctx->reader->EndRecPtr < end_of_wal)) { XLogRecord *record; char *errm = NULL; record = XLogReadRecord(ctx->reader, startptr, &errm); if (errm) elog(ERROR, "%s", errm); startptr = InvalidXLogRecPtr; /* * The {begin_txn,change,commit_txn}_wrapper callbacks above will * store the description into our tuplestore. */ if (record != NULL) LogicalDecodingProcessRecord(ctx, record); /* check limits */ if (upto_lsn != InvalidXLogRecPtr && upto_lsn <= ctx->reader->EndRecPtr) break; if (upto_nchanges != 0 && upto_nchanges <= p->returned_rows) break; } } PG_CATCH(); { /* clear all timetravel entries */ InvalidateSystemCaches(); PG_RE_THROW(); } PG_END_TRY(); tuplestore_donestoring(tupstore); CurrentResourceOwner = old_resowner; /* * Next time, start where we left off. (Hunting things, the family * business..) */ if (ctx->reader->EndRecPtr != InvalidXLogRecPtr && confirm) LogicalConfirmReceivedLocation(ctx->reader->EndRecPtr); /* free context, call shutdown callback */ FreeDecodingContext(ctx); ReplicationSlotRelease(); InvalidateSystemCaches(); return (Datum) 0; }
/* * get_func_input_arg_names * * Extract the names of input arguments only, given a function's * proargnames and proargmodes entries in Datum form. * * Returns the number of input arguments, which is the length of the * palloc'd array returned to *arg_names. Entries for unnamed args * are set to NULL. You don't get anything if proargnames is NULL. */ int get_func_input_arg_names(Datum proargnames, Datum proargmodes, char ***arg_names) { ArrayType *arr; int numargs; Datum *argnames; char *argmodes; char **inargnames; int numinargs; int i; /* Do nothing if null proargnames */ if (proargnames == PointerGetDatum(NULL)) { *arg_names = NULL; return 0; } /* * We expect the arrays to be 1-D arrays of the right types; verify that. * For proargmodes, we don't need to use deconstruct_array() since the * array data is just going to look like a C array of values. */ arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */ if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID) elog(ERROR, "proargnames is not a 1-D text array"); deconstruct_array(arr, TEXTOID, -1, false, 'i', &argnames, NULL, &numargs); if (proargmodes != PointerGetDatum(NULL)) { arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ if (ARR_NDIM(arr) != 1 || ARR_DIMS(arr)[0] != numargs || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID) elog(ERROR, "proargmodes is not a 1-D char array"); argmodes = (char *) ARR_DATA_PTR(arr); } else argmodes = NULL; /* zero elements probably shouldn't happen, but handle it gracefully */ if (numargs <= 0) { *arg_names = NULL; return 0; } /* extract input-argument names */ inargnames = (char **) palloc(numargs * sizeof(char *)); numinargs = 0; for (i = 0; i < numargs; i++) { if (argmodes == NULL || argmodes[i] == PROARGMODE_IN || argmodes[i] == PROARGMODE_INOUT || argmodes[i] == PROARGMODE_VARIADIC) { char *pname = TextDatumGetCString(argnames[i]); if (pname[0] != '\0') inargnames[numinargs] = pname; else inargnames[numinargs] = NULL; numinargs++; } } *arg_names = inargnames; return numinargs; }
/* * Create a new PLyProcedure structure */ static PLyProcedure * PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) { char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; PLyProcedure *volatile proc; MemoryContext cxt; MemoryContext oldcxt; int rv; char *ptr; procStruct = (Form_pg_proc) GETSTRUCT(procTup); rv = snprintf(procName, sizeof(procName), "__plpython_procedure_%s_%u", NameStr(procStruct->proname), fn_oid); if (rv >= sizeof(procName) || rv < 0) elog(ERROR, "procedure name would overrun buffer"); /* Replace any not-legal-in-Python-names characters with '_' */ for (ptr = procName; *ptr; ptr++) { if (!((*ptr >= 'A' && *ptr <= 'Z') || (*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9'))) *ptr = '_'; } cxt = AllocSetContextCreate(TopMemoryContext, procName, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); oldcxt = MemoryContextSwitchTo(cxt); proc = (PLyProcedure *) palloc0(sizeof(PLyProcedure)); proc->mcxt = cxt; PG_TRY(); { Datum protrftypes_datum; Datum prosrcdatum; bool isnull; char *procSource; int i; proc->proname = pstrdup(NameStr(procStruct->proname)); proc->pyname = pstrdup(procName); proc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data); proc->fn_tid = procTup->t_self; /* Remember if function is STABLE/IMMUTABLE */ proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE); PLy_typeinfo_init(&proc->result, proc->mcxt); for (i = 0; i < FUNC_MAX_ARGS; i++) PLy_typeinfo_init(&proc->args[i], proc->mcxt); proc->nargs = 0; proc->langid = procStruct->prolang; protrftypes_datum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_protrftypes, &isnull); proc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum); proc->code = proc->statics = NULL; proc->globals = NULL; proc->is_setof = procStruct->proretset; proc->setof = NULL; proc->src = NULL; proc->argnames = NULL; /* * get information required for output conversion of the return value, * but only if this isn't a trigger. */ if (!is_trigger) { HeapTuple rvTypeTup; Form_pg_type rvTypeStruct; rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(procStruct->prorettype)); if (!HeapTupleIsValid(rvTypeTup)) elog(ERROR, "cache lookup failed for type %u", procStruct->prorettype); rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup); /* Disallow pseudotype result, except for void or record */ if (rvTypeStruct->typtype == TYPTYPE_PSEUDO) { if (procStruct->prorettype == TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called as triggers"))); else if (procStruct->prorettype != VOIDOID && procStruct->prorettype != RECORDOID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Python functions cannot return type %s", format_type_be(procStruct->prorettype)))); } if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE || procStruct->prorettype == RECORDOID) { /* * Tuple: set up later, during first call to * PLy_function_handler */ proc->result.out.d.typoid = procStruct->prorettype; proc->result.out.d.typmod = -1; proc->result.is_rowtype = 2; } else { /* do the real work */ PLy_output_datum_func(&proc->result, rvTypeTup, proc->langid, proc->trftypes); } ReleaseSysCache(rvTypeTup); } /* * Now get information required for input conversion of the * procedure's arguments. Note that we ignore output arguments here. * If the function returns record, those I/O functions will be set up * when the function is first called. */ if (procStruct->pronargs) { Oid *types; char **names, *modes; int pos, total; /* extract argument type info from the pg_proc tuple */ total = get_func_arg_info(procTup, &types, &names, &modes); /* count number of in+inout args into proc->nargs */ if (modes == NULL) proc->nargs = total; else { /* proc->nargs was initialized to 0 above */ for (i = 0; i < total; i++) { if (modes[i] != PROARGMODE_OUT && modes[i] != PROARGMODE_TABLE) (proc->nargs)++; } } proc->argnames = (char **) palloc0(sizeof(char *) * proc->nargs); for (i = pos = 0; i < total; i++) { HeapTuple argTypeTup; Form_pg_type argTypeStruct; if (modes && (modes[i] == PROARGMODE_OUT || modes[i] == PROARGMODE_TABLE)) continue; /* skip OUT arguments */ Assert(types[i] == procStruct->proargtypes.values[pos]); argTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(types[i])); if (!HeapTupleIsValid(argTypeTup)) elog(ERROR, "cache lookup failed for type %u", types[i]); argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup); /* check argument type is OK, set up I/O function info */ switch (argTypeStruct->typtype) { case TYPTYPE_PSEUDO: /* Disallow pseudotype argument */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Python functions cannot accept type %s", format_type_be(types[i])))); break; case TYPTYPE_COMPOSITE: /* we'll set IO funcs at first call */ proc->args[pos].is_rowtype = 2; break; default: PLy_input_datum_func(&(proc->args[pos]), types[i], argTypeTup, proc->langid, proc->trftypes); break; } /* get argument name */ proc->argnames[pos] = names ? pstrdup(names[i]) : NULL; ReleaseSysCache(argTypeTup); pos++; } } /* * get the text of the function. */ prosrcdatum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); procSource = TextDatumGetCString(prosrcdatum); PLy_procedure_compile(proc, procSource); pfree(procSource); } PG_CATCH(); { MemoryContextSwitchTo(oldcxt); PLy_procedure_delete(proc); PG_RE_THROW(); } PG_END_TRY(); MemoryContextSwitchTo(oldcxt); return proc; }