static text* _nls_run_strxfrm(text *string, text *locale) { char *string_str; int string_len; char *locale_str = NULL; int locale_len = 0; text *result; char *tmp = NULL; size_t size = 0; size_t rest = 0; int changed_locale = 0; /* * Save the default, server-wide locale setting. * It should not change during the life-span of the server so it * is safe to save it only once, during the first invocation. */ if (!lc_collate_cache) { if ((lc_collate_cache = setlocale(LC_COLLATE, NULL))) /* Make a copy of the locale name string. */ lc_collate_cache = strdup(lc_collate_cache); if (!lc_collate_cache) elog(ERROR, "failed to retrieve the default LC_COLLATE value"); } /* * To run strxfrm, we need a zero-terminated strings. */ string_len = VARSIZE_ANY_EXHDR(string); if (string_len < 0) return NULL; string_str = palloc(string_len + 1); memcpy(string_str, VARDATA_ANY(string), string_len); *(string_str + string_len) = '\0'; if (locale) { locale_len = VARSIZE_ANY_EXHDR(locale); } /* * If different than default locale is requested, call setlocale. */ if (locale_len > 0 && (strncmp(lc_collate_cache, VARDATA_ANY(locale), locale_len) || *(lc_collate_cache + locale_len) != '\0')) { locale_str = palloc(locale_len + 1); memcpy(locale_str, VARDATA_ANY(locale), locale_len); *(locale_str + locale_len) = '\0'; /* * Try to set correct locales. * If setlocale failed, we know the default stayed the same, * co we can safely elog. */ if (!setlocale(LC_COLLATE, locale_str)) elog(ERROR, "failed to set the requested LC_COLLATE value [%s]", locale_str); changed_locale = 1; } /* * We do TRY / CATCH / END_TRY to catch ereport / elog that might * happen during palloc. Ereport during palloc would not be * nice since it would leave the server with changed locales * setting, resulting in bad things. */ PG_TRY(); { /* * Text transformation. * Increase the buffer until the strxfrm is able to fit. */ size = string_len * multiplication + 1; tmp = palloc(size + VARHDRSZ); rest = strxfrm(tmp + VARHDRSZ, string_str, size); while (rest >= size) { pfree(tmp); size = rest + 1; tmp = palloc(size + VARHDRSZ); rest = strxfrm(tmp + VARHDRSZ, string_str, size); /* * Cache the multiplication factor so that the next * time we start with better value. */ if (string_len) multiplication = (rest / string_len) + 2; } } PG_CATCH (); { if (changed_locale) { /* * Set original locale */ if (!setlocale(LC_COLLATE, lc_collate_cache)) elog(FATAL, "failed to set back the default LC_COLLATE value [%s]", lc_collate_cache); } } PG_END_TRY (); if (changed_locale) { /* * Set original locale */ if (!setlocale(LC_COLLATE, lc_collate_cache)) elog(FATAL, "failed to set back the default LC_COLLATE value [%s]", lc_collate_cache); pfree(locale_str); } pfree(string_str); /* * If the multiplication factor went down, reset it. */ if (string_len && rest < string_len * multiplication / 4) multiplication = (rest / string_len) + 1; result = (text *) tmp; SET_VARSIZE(result, rest + VARHDRSZ); return result; }
/* * Create a new file set * type is the WorkFileType for the files: BUFFILE or BFZ * can_be_reused: if set to false, then we don't insert this set into the cache, * since the caller is telling us there is no point. This can happen for * example when spilling during index creation. * ps is the PlanState for the subtree rooted at the operator * snapshot contains snapshot information for the current transaction * */ workfile_set * workfile_mgr_create_set(enum ExecWorkFileType type, bool can_be_reused, PlanState *ps, workfile_set_snapshot snapshot) { Assert(NULL != workfile_mgr_cache); Plan *plan = NULL; if (ps != NULL) { plan = ps->plan; } AssertImply(can_be_reused, plan != NULL); NodeTag node_type = T_Invalid; if (ps != NULL) { node_type = ps->type; } char *dir_path = create_workset_directory(node_type, currentSliceId); /* Create parameter info for the populate function */ workset_info set_info; set_info.file_type = type; set_info.snapshot = snapshot; set_info.nodeType = node_type; set_info.can_be_reused = can_be_reused && workfile_mgr_is_reusable(ps); set_info.dir_path = dir_path; set_info.session_start_time = GetCurrentTimestamp(); set_info.operator_work_mem = get_operator_work_mem(ps); set_info.on_disk = true; CacheEntry *newEntry = NULL; PG_TRY(); { newEntry = acquire_entry_retry(workfile_mgr_cache, &set_info); } PG_CATCH(); { /* Failed to acquire new entry, cache full. Clean up the directory we created. */ workfile_mgr_delete_set_directory(dir_path); PG_RE_THROW(); } PG_END_TRY(); /* Path has now been copied to the workfile_set. We can free it */ pfree(dir_path); /* Complete initialization of the entry with post-acquire actions */ Assert(NULL != newEntry); workfile_set *work_set = CACHE_ENTRY_PAYLOAD(newEntry); Assert(work_set != NULL); if (work_set->can_be_reused) { Assert(plan != NULL); Assert(nodeTag(plan) >= T_Plan && nodeTag(plan) < T_PlanInvalItem); workfile_set_plan *s_plan = workfile_mgr_serialize_plan(ps); work_set->key = workfile_mgr_hash_key(s_plan); workfile_mgr_save_plan(work_set, s_plan); workfile_mgr_free_plan(s_plan); } elog(gp_workfile_caching_loglevel, "new spill file set. key=0x%x can_be_reused=%d prefix=%s opMemKB=" INT64_FORMAT, work_set->key, work_set->can_be_reused, work_set->path, work_set->metadata.operator_work_mem); return work_set; }
/* * SearchCatCacheList * * Generate a list of all tuples matching a partial key (that is, * a key specifying just the first K of the cache's N key columns). * * The caller must not modify the list object or the pointed-to tuples, * and must call ReleaseCatCacheList() when done with the list. */ CatCList * SearchCatCacheList(CatCache *cache, int nkeys, Datum v1, Datum v2, Datum v3, Datum v4) { ScanKeyData cur_skey[CATCACHE_MAXKEYS]; uint32 lHashValue; Dlelem *elt; CatCList *cl; CatCTup *ct; List *volatile ctlist; ListCell *ctlist_item; int nmembers; bool ordered; HeapTuple ntp; MemoryContext oldcxt; int i; /* * one-time startup overhead for each cache */ if (cache->cc_tupdesc == NULL) CatalogCacheInitializeCache(cache); Assert(nkeys > 0 && nkeys < cache->cc_nkeys); #ifdef CATCACHE_STATS cache->cc_lsearches++; #endif /* * initialize the search key information */ memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey)); cur_skey[0].sk_argument = v1; cur_skey[1].sk_argument = v2; cur_skey[2].sk_argument = v3; cur_skey[3].sk_argument = v4; /* * compute a hash value of the given keys for faster search. We don't * presently divide the CatCList items into buckets, but this still lets * us skip non-matching items quickly most of the time. */ lHashValue = CatalogCacheComputeHashValue(cache, nkeys, cur_skey); /* * scan the items until we find a match or exhaust our list */ for (elt = DLGetHead(&cache->cc_lists); elt; elt = DLGetSucc(elt)) { bool res; cl = (CatCList *) DLE_VAL(elt); if (cl->dead) continue; /* ignore dead entries */ if (cl->hash_value != lHashValue) continue; /* quickly skip entry if wrong hash val */ /* * see if the cached list matches our key. */ if (cl->nkeys != nkeys) continue; HeapKeyTest(&cl->tuple, cache->cc_tupdesc, nkeys, cur_skey, &res); if (!res) continue; /* * We found a matching list. Move the list to the front of the * cache's list-of-lists, to speed subsequent searches. (We do not * move the members to the fronts of their hashbucket lists, however, * since there's no point in that unless they are searched for * individually.) */ DLMoveToFront(&cl->cache_elem); /* Bump the list's refcount and return it */ ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); cl->refcount++; ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); CACHE2_elog(DEBUG2, "SearchCatCacheList(%s): found list", cache->cc_relname); #ifdef CATCACHE_STATS cache->cc_lhits++; #endif return cl; } /* * List was not found in cache, so we have to build it by reading the * relation. For each matching tuple found in the relation, use an * existing cache entry if possible, else build a new one. * * We have to bump the member refcounts temporarily to ensure they won't * get dropped from the cache while loading other members. We use a PG_TRY * block to ensure we can undo those refcounts if we get an error before * we finish constructing the CatCList. */ ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); ctlist = NIL; PG_TRY(); { Relation relation; SysScanDesc scandesc; relation = heap_open(cache->cc_reloid, AccessShareLock); scandesc = systable_beginscan(relation, cache->cc_indexoid, IndexScanOK(cache, cur_skey), SnapshotNow, nkeys, cur_skey); /* The list will be ordered iff we are doing an index scan */ ordered = (scandesc->irel != NULL); while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) { uint32 hashValue; Index hashIndex; /* * See if there's an entry for this tuple already. */ ct = NULL; hashValue = CatalogCacheComputeTupleHashValue(cache, ntp); hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); for (elt = DLGetHead(&cache->cc_bucket[hashIndex]); elt; elt = DLGetSucc(elt)) { ct = (CatCTup *) DLE_VAL(elt); if (ct->dead || ct->negative) continue; /* ignore dead and negative entries */ if (ct->hash_value != hashValue) continue; /* quickly skip entry if wrong hash val */ if (!ItemPointerEquals(&(ct->tuple.t_self), &(ntp->t_self))) continue; /* not same tuple */ /* * Found a match, but can't use it if it belongs to another * list already */ if (ct->c_list) continue; break; /* A-OK */ } if (elt == NULL) { /* We didn't find a usable entry, so make a new one */ ct = CatalogCacheCreateEntry(cache, ntp, hashValue, hashIndex, false); } /* Careful here: add entry to ctlist, then bump its refcount */ /* This way leaves state correct if lappend runs out of memory */ ctlist = lappend(ctlist, ct); ct->refcount++; } systable_endscan(scandesc); heap_close(relation, AccessShareLock); /* * Now we can build the CatCList entry. First we need a dummy tuple * containing the key values... */ ntp = build_dummy_tuple(cache, nkeys, cur_skey); oldcxt = MemoryContextSwitchTo(CacheMemoryContext); nmembers = list_length(ctlist); cl = (CatCList *) palloc(sizeof(CatCList) + nmembers * sizeof(CatCTup *)); heap_copytuple_with_tuple(ntp, &cl->tuple); MemoryContextSwitchTo(oldcxt); heap_freetuple(ntp); /* * We are now past the last thing that could trigger an elog before we * have finished building the CatCList and remembering it in the * resource owner. So it's OK to fall out of the PG_TRY, and indeed * we'd better do so before we start marking the members as belonging * to the list. */ } PG_CATCH(); { foreach(ctlist_item, ctlist) { ct = (CatCTup *) lfirst(ctlist_item); Assert(ct->c_list == NULL); Assert(ct->refcount > 0); ct->refcount--; if ( #ifndef CATCACHE_FORCE_RELEASE ct->dead && #endif ct->refcount == 0 && (ct->c_list == NULL || ct->c_list->refcount == 0)) CatCacheRemoveCTup(cache, ct); } PG_RE_THROW(); }
/* * ProcessCatchupEvent * * Respond to a catchup event (PROCSIG_CATCHUP_INTERRUPT) from another * backend. * * This is called either directly from the PROCSIG_CATCHUP_INTERRUPT * signal handler, or the next time control reaches the outer idle loop * (assuming there's still anything to do by then). */ static void ProcessCatchupEvent(void) { bool notify_enabled; bool client_wait_timeout_enabled; DtxContext saveDistributedTransactionContext; /* * Funny indentation to keep the code inside identical to upstream * while at the same time supporting CMockery which has problems with * multiple bracing on column 1. */ PG_TRY(); { in_process_catchup_event = 1; /* Must prevent SIGUSR2 and SIGALRM(for IdleSessionGangTimeout) interrupt while I am running */ notify_enabled = DisableNotifyInterrupt(); client_wait_timeout_enabled = DisableClientWaitTimeoutInterrupt(); /* * What we need to do here is cause ReceiveSharedInvalidMessages() to run, * which will do the necessary work and also reset the * catchupInterruptOccurred flag. If we are inside a transaction we can * just call AcceptInvalidationMessages() to do this. If we aren't, we * start and immediately end a transaction; the call to * AcceptInvalidationMessages() happens down inside transaction start. * * It is awfully tempting to just call AcceptInvalidationMessages() * without the rest of the xact start/stop overhead, and I think that * would actually work in the normal case; but I am not sure that things * would clean up nicely if we got an error partway through. */ if (IsTransactionOrTransactionBlock()) { elog(DEBUG1, "ProcessCatchupEvent inside transaction"); AcceptInvalidationMessages(); } else { elog(DEBUG1, "ProcessCatchupEvent outside transaction"); /* * Save distributed transaction context first. */ saveDistributedTransactionContext = DistributedTransactionContext; DistributedTransactionContext = DTX_CONTEXT_LOCAL_ONLY; StartTransactionCommand(); CommitTransactionCommand(); DistributedTransactionContext = saveDistributedTransactionContext; } if (notify_enabled) EnableNotifyInterrupt(); if (client_wait_timeout_enabled) EnableClientWaitTimeoutInterrupt(); in_process_catchup_event = 0; } PG_CATCH(); { in_process_catchup_event = 0; PG_RE_THROW(); } PG_END_TRY(); }
/* * Emit a PG error or notice, together with any available info about * the current Python error, previously set by PLy_exception_set(). * This should be used to propagate Python errors into PG. If fmt is * NULL, the Python error becomes the primary error message, otherwise * it becomes the detail. If there is a Python traceback, it is put * in the context. */ void PLy_elog(int elevel, const char *fmt,...) { char *xmsg; char *tbmsg; int tb_depth; StringInfoData emsg; PyObject *exc, *val, *tb; const char *primary = NULL; int sqlerrcode = 0; char *detail = NULL; char *hint = NULL; char *query = NULL; int position = 0; PyErr_Fetch(&exc, &val, &tb); if (exc != NULL) { PyErr_NormalizeException(&exc, &val, &tb); if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position); else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) elevel = FATAL; } /* this releases our refcount on tb! */ PLy_traceback(exc, val, tb, &xmsg, &tbmsg, &tb_depth); if (fmt) { initStringInfo(&emsg); for (;;) { va_list ap; int needed; va_start(ap, fmt); needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); va_end(ap); if (needed == 0) break; enlargeStringInfo(&emsg, needed); } primary = emsg.data; /* Since we have a format string, we cannot have a SPI detail. */ Assert(detail == NULL); /* If there's an exception message, it goes in the detail. */ if (xmsg) detail = xmsg; } else { if (xmsg) primary = xmsg; } PG_TRY(); { ereport(elevel, (errcode(sqlerrcode ? sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), errmsg_internal("%s", primary ? primary : "no exception data"), (detail) ? errdetail_internal("%s", detail) : 0, (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0, (hint) ? errhint("%s", hint) : 0, (query) ? internalerrquery(query) : 0, (position) ? internalerrposition(position) : 0)); } PG_CATCH(); { if (fmt) pfree(emsg.data); if (xmsg) pfree(xmsg); if (tbmsg) pfree(tbmsg); Py_XDECREF(exc); Py_XDECREF(val); PG_RE_THROW(); } PG_END_TRY(); if (fmt) pfree(emsg.data); if (xmsg) pfree(xmsg); if (tbmsg) pfree(tbmsg); Py_XDECREF(exc); Py_XDECREF(val); }
/* * 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 and maintain ThisTimeLineID. * RecoveryInProgress() will update ThisTimeLineID on promotion. */ if (!RecoveryInProgress()) end_of_wal = GetFlushRecPtr(); else end_of_wal = GetXLogReplayRecPtr(&ThisTimeLineID); 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(); /* Decode until we run out of records */ 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; }
/* * Primary entry point for VACUUM and ANALYZE commands. * * relid is normally InvalidOid; if it is not, then it provides the relation * OID to be processed, and vacstmt->relation is ignored. (The non-invalid * case is currently only used by autovacuum.) * * do_toast is passed as FALSE by autovacuum, because it processes TOAST * tables separately. * * for_wraparound is used by autovacuum to let us know when it's forcing * a vacuum for wraparound, which should not be auto-canceled. * * bstrategy is normally given as NULL, but in autovacuum it can be passed * in to use the same buffer strategy object across multiple vacuum() calls. * * isTopLevel should be passed down from ProcessUtility. * * It is the caller's responsibility that vacstmt and bstrategy * (if given) be allocated in a memory context that won't disappear * at transaction commit. */ void vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel) { const char *stmttype; volatile bool in_outer_xact, use_own_xacts; List *relations; static bool in_vacuum = false; /* sanity checks on options */ Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE)); Assert((vacstmt->options & VACOPT_VACUUM) || !(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE))); Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL); stmttype = (vacstmt->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE"; /* * We cannot run VACUUM inside a user transaction block; if we were inside * a transaction, then our commit- and start-transaction-command calls * would not have the intended effect! There are numerous other subtle * dependencies on this, too. * * ANALYZE (without VACUUM) can run either way. */ if (vacstmt->options & VACOPT_VACUUM) { PreventTransactionChain(isTopLevel, stmttype); in_outer_xact = false; } else in_outer_xact = IsInTransactionChain(isTopLevel); /* * Due to static variables vac_context, anl_context and vac_strategy, * vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE * calls a hostile index expression that itself calls ANALYZE. */ if (in_vacuum) elog(ERROR, "%s cannot be executed from VACUUM or ANALYZE", stmttype); /* * Send info about dead objects to the statistics collector, unless we are * in autovacuum --- autovacuum.c does this for itself. */ if ((vacstmt->options & VACOPT_VACUUM) && !IsAutoVacuumWorkerProcess()) pgstat_vacuum_stat(); /* * Create special memory context for cross-transaction storage. * * Since it is a child of PortalContext, it will go away eventually even * if we suffer an error; there's no need for special abort cleanup logic. */ vac_context = AllocSetContextCreate(PortalContext, "Vacuum", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* * If caller didn't give us a buffer strategy object, make one in the * cross-transaction memory context. */ if (bstrategy == NULL) { MemoryContext old_context = MemoryContextSwitchTo(vac_context); bstrategy = GetAccessStrategy(BAS_VACUUM); MemoryContextSwitchTo(old_context); } vac_strategy = bstrategy; /* * Build list of relations to process, unless caller gave us one. (If we * build one, we put it in vac_context for safekeeping.) */ relations = get_rel_oids(relid, vacstmt->relation); /* * Decide whether we need to start/commit our own transactions. * * For VACUUM (with or without ANALYZE): always do so, so that we can * release locks as soon as possible. (We could possibly use the outer * transaction for a one-table VACUUM, but handling TOAST tables would be * problematic.) * * For ANALYZE (no VACUUM): if inside a transaction block, we cannot * start/commit our own transactions. Also, there's no need to do so if * only processing one relation. For multiple relations when not within a * transaction block, and also in an autovacuum worker, use own * transactions so we can release locks sooner. */ if (vacstmt->options & VACOPT_VACUUM) use_own_xacts = true; else { Assert(vacstmt->options & VACOPT_ANALYZE); if (IsAutoVacuumWorkerProcess()) use_own_xacts = true; else if (in_outer_xact) use_own_xacts = false; else if (list_length(relations) > 1) use_own_xacts = true; else use_own_xacts = false; } /* * vacuum_rel expects to be entered with no transaction active; it will * start and commit its own transaction. But we are called by an SQL * command, and so we are executing inside a transaction already. We * commit the transaction started in PostgresMain() here, and start * another one before exiting to match the commit waiting for us back in * PostgresMain(). */ if (use_own_xacts) { Assert(!in_outer_xact); /* ActiveSnapshot is not set by autovacuum */ if (ActiveSnapshotSet()) PopActiveSnapshot(); /* matches the StartTransaction in PostgresMain() */ CommitTransactionCommand(); } /* Turn vacuum cost accounting on or off */ PG_TRY(); { ListCell *cur; in_vacuum = true; VacuumCostActive = (VacuumCostDelay > 0); VacuumCostBalance = 0; VacuumPageHit = 0; VacuumPageMiss = 0; VacuumPageDirty = 0; /* * Loop to process each selected relation. */ foreach(cur, relations) { Oid relid = lfirst_oid(cur); if (vacstmt->options & VACOPT_VACUUM) { if (!vacuum_rel(relid, vacstmt, do_toast, for_wraparound)) continue; } if (vacstmt->options & VACOPT_ANALYZE) { /* * If using separate xacts, start one for analyze. Otherwise, * we can use the outer transaction. */ if (use_own_xacts) { StartTransactionCommand(); /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); } analyze_rel(relid, vacstmt, in_outer_xact, vac_strategy); if (use_own_xacts) { PopActiveSnapshot(); CommitTransactionCommand(); } } } }
/* * plr_SPI_exec - The builtin SPI_exec command for the R interpreter */ SEXP plr_SPI_exec(SEXP rsql) { int spi_rc = 0; char buf[64]; const char *sql; int count = 0; int ntuples; SEXP result = NULL; MemoryContext oldcontext; PREPARE_PG_TRY; /* set up error context */ PUSH_PLERRCONTEXT(rsupport_error_callback, "pg.spi.exec"); PROTECT(rsql = AS_CHARACTER(rsql)); sql = CHAR(STRING_ELT(rsql, 0)); UNPROTECT(1); if (sql == NULL) error("%s", "cannot exec empty query"); /* switch to SPI memory context */ SWITCHTO_PLR_SPI_CONTEXT(oldcontext); /* * trap elog/ereport so we can let R finish up gracefully * and generate the error once we exit the interpreter */ PG_TRY(); { /* Execute the query and handle return codes */ spi_rc = SPI_exec(sql, count); } PLR_PG_CATCH(); PLR_PG_END_TRY(); /* back to caller's memory context */ MemoryContextSwitchTo(oldcontext); switch (spi_rc) { case SPI_OK_UTILITY: snprintf(buf, sizeof(buf), "%d", 0); SPI_freetuptable(SPI_tuptable); PROTECT(result = NEW_CHARACTER(1)); SET_STRING_ELT(result, 0, COPY_TO_USER_STRING(buf)); UNPROTECT(1); break; case SPI_OK_SELINTO: case SPI_OK_INSERT: case SPI_OK_DELETE: case SPI_OK_UPDATE: snprintf(buf, sizeof(buf), "%d", SPI_processed); SPI_freetuptable(SPI_tuptable); PROTECT(result = NEW_CHARACTER(1)); SET_STRING_ELT(result, 0, COPY_TO_USER_STRING(buf)); UNPROTECT(1); break; case SPI_OK_SELECT: ntuples = SPI_processed; if (ntuples > 0) { result = rpgsql_get_results(ntuples, SPI_tuptable); SPI_freetuptable(SPI_tuptable); } else result = R_NilValue; break; case SPI_ERROR_ARGUMENT: error("SPI_exec() failed: SPI_ERROR_ARGUMENT"); break; case SPI_ERROR_UNCONNECTED: error("SPI_exec() failed: SPI_ERROR_UNCONNECTED"); break; case SPI_ERROR_COPY: error("SPI_exec() failed: SPI_ERROR_COPY"); break; case SPI_ERROR_CURSOR: error("SPI_exec() failed: SPI_ERROR_CURSOR"); break; case SPI_ERROR_TRANSACTION: error("SPI_exec() failed: SPI_ERROR_TRANSACTION"); break; case SPI_ERROR_OPUNKNOWN: error("SPI_exec() failed: SPI_ERROR_OPUNKNOWN"); break; default: error("SPI_exec() failed: %d", spi_rc); break; } POP_PLERRCONTEXT; return result; }
/* * plr_SPI_prepare - The builtin SPI_prepare command for the R interpreter */ SEXP plr_SPI_prepare(SEXP rsql, SEXP rargtypes) { const char *sql; int nargs; int i; Oid *typeids = NULL; Oid *typelems = NULL; FmgrInfo *typinfuncs = NULL; void *pplan = NULL; void *saved_plan; saved_plan_desc *plan_desc; SEXP result; MemoryContext oldcontext; PREPARE_PG_TRY; /* set up error context */ PUSH_PLERRCONTEXT(rsupport_error_callback, "pg.spi.prepare"); /* switch to long lived context to create plan description */ oldcontext = MemoryContextSwitchTo(TopMemoryContext); plan_desc = (saved_plan_desc *) palloc(sizeof(saved_plan_desc)); MemoryContextSwitchTo(oldcontext); PROTECT(rsql = AS_CHARACTER(rsql)); sql = CHAR(STRING_ELT(rsql, 0)); UNPROTECT(1); if (sql == NULL) error("%s", "cannot prepare empty query"); PROTECT(rargtypes = AS_INTEGER(rargtypes)); if (!isVector(rargtypes) || !isInteger(rargtypes)) error("%s", "second parameter must be a vector of PostgreSQL datatypes"); /* deal with case of no parameters for the prepared query */ if (rargtypes == R_MissingArg || INTEGER(rargtypes)[0] == NA_INTEGER) nargs = 0; else nargs = length(rargtypes); if (nargs < 0) /* can this even happen?? */ error("%s", "second parameter must be a vector of PostgreSQL datatypes"); if (nargs > 0) { /* switch to long lived context to create plan description elements */ oldcontext = MemoryContextSwitchTo(TopMemoryContext); typeids = (Oid *) palloc(nargs * sizeof(Oid)); typelems = (Oid *) palloc(nargs * sizeof(Oid)); typinfuncs = (FmgrInfo *) palloc(nargs * sizeof(FmgrInfo)); MemoryContextSwitchTo(oldcontext); for (i = 0; i < nargs; i++) { int16 typlen; bool typbyval; char typdelim; Oid typinput, typelem; char typalign; FmgrInfo typinfunc; typeids[i] = INTEGER(rargtypes)[i]; /* switch to long lived context to create plan description elements */ oldcontext = MemoryContextSwitchTo(TopMemoryContext); get_type_io_data(typeids[i], IOFunc_input, &typlen, &typbyval, &typalign, &typdelim, &typelem, &typinput); typelems[i] = get_element_type(typeids[i]); MemoryContextSwitchTo(oldcontext); /* perm_fmgr_info already uses TopMemoryContext */ perm_fmgr_info(typinput, &typinfunc); typinfuncs[i] = typinfunc; } } else typeids = NULL; UNPROTECT(1); /* switch to SPI memory context */ SWITCHTO_PLR_SPI_CONTEXT(oldcontext); /* * trap elog/ereport so we can let R finish up gracefully * and generate the error once we exit the interpreter */ PG_TRY(); { /* Prepare plan for query */ pplan = SPI_prepare(sql, nargs, typeids); } PLR_PG_CATCH(); PLR_PG_END_TRY(); if (pplan == NULL) { char buf[128]; char *reason; switch (SPI_result) { case SPI_ERROR_ARGUMENT: reason = "SPI_ERROR_ARGUMENT"; break; case SPI_ERROR_UNCONNECTED: reason = "SPI_ERROR_UNCONNECTED"; break; case SPI_ERROR_COPY: reason = "SPI_ERROR_COPY"; break; case SPI_ERROR_CURSOR: reason = "SPI_ERROR_CURSOR"; break; case SPI_ERROR_TRANSACTION: reason = "SPI_ERROR_TRANSACTION"; break; case SPI_ERROR_OPUNKNOWN: reason = "SPI_ERROR_OPUNKNOWN"; break; default: snprintf(buf, sizeof(buf), "unknown RC %d", SPI_result); reason = buf; break; } /* internal error */ error("SPI_prepare() failed: %s", reason); } /* SPI_saveplan already uses TopMemoryContext */ saved_plan = SPI_saveplan(pplan); if (saved_plan == NULL) { char buf[128]; char *reason; switch (SPI_result) { case SPI_ERROR_ARGUMENT: reason = "SPI_ERROR_ARGUMENT"; break; case SPI_ERROR_UNCONNECTED: reason = "SPI_ERROR_UNCONNECTED"; break; default: snprintf(buf, sizeof(buf), "unknown RC %d", SPI_result); reason = buf; break; } /* internal error */ error("SPI_saveplan() failed: %s", reason); } /* back to caller's memory context */ MemoryContextSwitchTo(oldcontext); /* no longer need this */ SPI_freeplan(pplan); plan_desc->saved_plan = saved_plan; plan_desc->nargs = nargs; plan_desc->typeids = typeids; plan_desc->typelems = typelems; plan_desc->typinfuncs = typinfuncs; result = R_MakeExternalPtr(plan_desc, R_NilValue, R_NilValue); POP_PLERRCONTEXT; return result; }
static PyObject * PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv) { TriggerData *tdata = (TriggerData *) fcinfo->context; PyObject *pltname, *pltevent, *pltwhen, *pltlevel, *pltrelid, *plttablename, *plttableschema; PyObject *pltargs, *pytnew, *pytold; PyObject *volatile pltdata = NULL; char *stroid; PG_TRY(); { pltdata = PyDict_New(); if (!pltdata) PLy_elog(ERROR, "could not create new dictionary while building trigger arguments"); pltname = PyString_FromString(tdata->tg_trigger->tgname); PyDict_SetItemString(pltdata, "name", pltname); Py_DECREF(pltname); stroid = DatumGetCString(DirectFunctionCall1(oidout, ObjectIdGetDatum(tdata->tg_relation->rd_id))); pltrelid = PyString_FromString(stroid); PyDict_SetItemString(pltdata, "relid", pltrelid); Py_DECREF(pltrelid); pfree(stroid); stroid = SPI_getrelname(tdata->tg_relation); plttablename = PyString_FromString(stroid); PyDict_SetItemString(pltdata, "table_name", plttablename); Py_DECREF(plttablename); pfree(stroid); stroid = SPI_getnspname(tdata->tg_relation); plttableschema = PyString_FromString(stroid); PyDict_SetItemString(pltdata, "table_schema", plttableschema); Py_DECREF(plttableschema); pfree(stroid); if (TRIGGER_FIRED_BEFORE(tdata->tg_event)) pltwhen = PyString_FromString("BEFORE"); else if (TRIGGER_FIRED_AFTER(tdata->tg_event)) pltwhen = PyString_FromString("AFTER"); else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event)) pltwhen = PyString_FromString("INSTEAD OF"); else { elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event); pltwhen = NULL; /* keep compiler quiet */ } PyDict_SetItemString(pltdata, "when", pltwhen); Py_DECREF(pltwhen); if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) { pltlevel = PyString_FromString("ROW"); PyDict_SetItemString(pltdata, "level", pltlevel); Py_DECREF(pltlevel); if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) { pltevent = PyString_FromString("INSERT"); PyDict_SetItemString(pltdata, "old", Py_None); pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); *rv = tdata->tg_trigtuple; } else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) { pltevent = PyString_FromString("DELETE"); PyDict_SetItemString(pltdata, "new", Py_None); pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "old", pytold); Py_DECREF(pytold); *rv = tdata->tg_trigtuple; } else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) { pltevent = PyString_FromString("UPDATE"); pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "old", pytold); Py_DECREF(pytold); *rv = tdata->tg_newtuple; } else { elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); pltevent = NULL; /* keep compiler quiet */ } PyDict_SetItemString(pltdata, "event", pltevent); Py_DECREF(pltevent); } else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event)) { pltlevel = PyString_FromString("STATEMENT"); PyDict_SetItemString(pltdata, "level", pltlevel); Py_DECREF(pltlevel); PyDict_SetItemString(pltdata, "old", Py_None); PyDict_SetItemString(pltdata, "new", Py_None); *rv = NULL; if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) pltevent = PyString_FromString("INSERT"); else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) pltevent = PyString_FromString("DELETE"); else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) pltevent = PyString_FromString("UPDATE"); else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) pltevent = PyString_FromString("TRUNCATE"); else { elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); pltevent = NULL; /* keep compiler quiet */ } PyDict_SetItemString(pltdata, "event", pltevent); Py_DECREF(pltevent); } else elog(ERROR, "unrecognized LEVEL tg_event: %u", tdata->tg_event); if (tdata->tg_trigger->tgnargs) { /* * all strings... */ int i; PyObject *pltarg; pltargs = PyList_New(tdata->tg_trigger->tgnargs); for (i = 0; i < tdata->tg_trigger->tgnargs; i++) { pltarg = PyString_FromString(tdata->tg_trigger->tgargs[i]); /* * stolen, don't Py_DECREF */ PyList_SetItem(pltargs, i, pltarg); } } else { Py_INCREF(Py_None); pltargs = Py_None; } PyDict_SetItemString(pltdata, "args", pltargs); Py_DECREF(pltargs); } PG_CATCH(); { Py_XDECREF(pltdata); PG_RE_THROW(); } PG_END_TRY(); return pltdata; }
static HeapTuple PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, HeapTuple otup) { PyObject *volatile plntup; PyObject *volatile plkeys; PyObject *volatile plval; HeapTuple rtup; int natts, i, attn, atti; int *volatile modattrs; Datum *volatile modvalues; char *volatile modnulls; TupleDesc tupdesc; ErrorContextCallback plerrcontext; plerrcontext.callback = plpython_trigger_error_callback; plerrcontext.previous = error_context_stack; error_context_stack = &plerrcontext; plntup = plkeys = plval = NULL; modattrs = NULL; modvalues = NULL; modnulls = NULL; PG_TRY(); { if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL) ereport(ERROR, (errmsg("TD[\"new\"] deleted, cannot modify row"))); Py_INCREF(plntup); if (!PyDict_Check(plntup)) ereport(ERROR, (errmsg("TD[\"new\"] is not a dictionary"))); plkeys = PyDict_Keys(plntup); natts = PyList_Size(plkeys); modattrs = (int *) palloc(natts * sizeof(int)); modvalues = (Datum *) palloc(natts * sizeof(Datum)); modnulls = (char *) palloc(natts * sizeof(char)); tupdesc = tdata->tg_relation->rd_att; for (i = 0; i < natts; i++) { PyObject *platt; char *plattstr; platt = PyList_GetItem(plkeys, i); if (PyString_Check(platt)) plattstr = PyString_AsString(platt); else if (PyUnicode_Check(platt)) plattstr = PLyUnicode_AsString(platt); else { ereport(ERROR, (errmsg("TD[\"new\"] dictionary key at ordinal position %d is not a string", i))); plattstr = NULL; /* keep compiler quiet */ } attn = SPI_fnumber(tupdesc, plattstr); if (attn == SPI_ERROR_NOATTRIBUTE) ereport(ERROR, (errmsg("key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row", plattstr))); atti = attn - 1; plval = PyDict_GetItem(plntup, platt); if (plval == NULL) elog(FATAL, "Python interpreter is probably corrupted"); Py_INCREF(plval); modattrs[i] = attn; if (tupdesc->attrs[atti]->attisdropped) { modvalues[i] = (Datum) 0; modnulls[i] = 'n'; } else if (plval != Py_None) { PLyObToDatum *att = &proc->result.out.r.atts[atti]; modvalues[i] = (att->func) (att, tupdesc->attrs[atti]->atttypmod, plval); modnulls[i] = ' '; } else { modvalues[i] = InputFunctionCall(&proc->result.out.r.atts[atti].typfunc, NULL, proc->result.out.r.atts[atti].typioparam, tupdesc->attrs[atti]->atttypmod); modnulls[i] = 'n'; } Py_DECREF(plval); plval = NULL; } rtup = SPI_modifytuple(tdata->tg_relation, otup, natts, modattrs, modvalues, modnulls); if (rtup == NULL) elog(ERROR, "SPI_modifytuple failed: error %d", SPI_result); } PG_CATCH(); { Py_XDECREF(plntup); Py_XDECREF(plkeys); Py_XDECREF(plval); if (modnulls) pfree(modnulls); if (modvalues) pfree(modvalues); if (modattrs) pfree(modattrs); PG_RE_THROW(); } PG_END_TRY(); Py_DECREF(plntup); Py_DECREF(plkeys); pfree(modattrs); pfree(modvalues); pfree(modnulls); error_context_stack = plerrcontext.previous; return rtup; }
/* function subhandler */ Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc) { Datum rv; PyObject *volatile plargs = NULL; PyObject *volatile plrv = NULL; ErrorContextCallback plerrcontext; PG_TRY(); { if (!proc->is_setof || proc->setof == NULL) { /* * Simple type returning function or first time for SETOF * function: actually execute the function. */ plargs = PLy_function_build_args(fcinfo, proc); plrv = PLy_procedure_call(proc, "args", plargs); if (!proc->is_setof) { /* * SETOF function parameters will be deleted when last row is * returned */ PLy_function_delete_args(proc); } Assert(plrv != NULL); } /* * If it returns a set, call the iterator to get the next return item. * We stay in the SPI context while doing this, because PyIter_Next() * calls back into Python code which might contain SPI calls. */ if (proc->is_setof) { bool has_error = false; ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; if (proc->setof == NULL) { /* first time -- do checks and setup */ if (!rsi || !IsA(rsi, ReturnSetInfo) || (rsi->allowedModes & SFRM_ValuePerCall) == 0) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("unsupported set function return mode"), errdetail("PL/Python set-returning functions only support returning only value per call."))); } rsi->returnMode = SFRM_ValuePerCall; /* Make iterator out of returned object */ proc->setof = PyObject_GetIter(plrv); Py_DECREF(plrv); plrv = NULL; if (proc->setof == NULL) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("returned object cannot be iterated"), errdetail("PL/Python set-returning functions must return an iterable object."))); } /* Fetch next from iterator */ plrv = PyIter_Next(proc->setof); if (plrv) rsi->isDone = ExprMultipleResult; else { rsi->isDone = ExprEndResult; has_error = PyErr_Occurred() != NULL; } if (rsi->isDone == ExprEndResult) { /* Iterator is exhausted or error happened */ Py_DECREF(proc->setof); proc->setof = NULL; Py_XDECREF(plargs); Py_XDECREF(plrv); PLy_function_delete_args(proc); if (has_error) PLy_elog(ERROR, "error fetching next item from iterator"); /* Disconnect from the SPI manager before returning */ if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); fcinfo->isnull = true; return (Datum) NULL; } } /* * Disconnect from SPI manager and then create the return values datum * (if the input function does a palloc for it this must not be * allocated in the SPI memory context because SPI_finish would free * it). */ if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); plerrcontext.callback = plpython_return_error_callback; plerrcontext.previous = error_context_stack; error_context_stack = &plerrcontext; /* * If the function is declared to return void, the Python return value * must be None. For void-returning functions, we also treat a None * return value as a special "void datum" rather than NULL (as is the * case for non-void-returning functions). */ if (proc->result.out.d.typoid == VOIDOID) { if (plrv != Py_None) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("PL/Python function with return type \"void\" did not return None"))); fcinfo->isnull = false; rv = (Datum) 0; } else if (plrv == Py_None) { fcinfo->isnull = true; if (proc->result.is_rowtype < 1) rv = InputFunctionCall(&proc->result.out.d.typfunc, NULL, proc->result.out.d.typioparam, -1); else /* Tuple as None */ rv = (Datum) NULL; } else if (proc->result.is_rowtype >= 1) { TupleDesc desc; /* make sure it's not an unnamed record */ Assert((proc->result.out.d.typoid == RECORDOID && proc->result.out.d.typmod != -1) || (proc->result.out.d.typoid != RECORDOID && proc->result.out.d.typmod == -1)); desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid, proc->result.out.d.typmod); rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv); fcinfo->isnull = (rv == (Datum) NULL); } else { fcinfo->isnull = false; rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv); } } PG_CATCH(); { Py_XDECREF(plargs); Py_XDECREF(plrv); /* * If there was an error the iterator might have not been exhausted * yet. Set it to NULL so the next invocation of the function will * start the iteration again. */ Py_XDECREF(proc->setof); proc->setof = NULL; PG_RE_THROW(); } PG_END_TRY(); error_context_stack = plerrcontext.previous; Py_XDECREF(plargs); Py_DECREF(plrv); return rv; }
static PyObject * PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc) { PyObject *volatile arg = NULL; PyObject *volatile args = NULL; int i; PG_TRY(); { args = PyList_New(proc->nargs); for (i = 0; i < proc->nargs; i++) { if (proc->args[i].is_rowtype > 0) { if (fcinfo->argnull[i]) arg = NULL; else { HeapTupleHeader td; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tmptup; td = DatumGetHeapTupleHeader(fcinfo->arg[i]); /* Extract rowtype info and find a tupdesc */ tupType = HeapTupleHeaderGetTypeId(td); tupTypmod = HeapTupleHeaderGetTypMod(td); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); /* Set up I/O funcs if not done yet */ if (proc->args[i].is_rowtype != 1) PLy_input_tuple_funcs(&(proc->args[i]), tupdesc); /* Build a temporary HeapTuple control structure */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc); ReleaseTupleDesc(tupdesc); } } else { if (fcinfo->argnull[i]) arg = NULL; else { arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d), fcinfo->arg[i]); } } if (arg == NULL) { Py_INCREF(Py_None); arg = Py_None; } if (PyList_SetItem(args, i, arg) == -1) PLy_elog(ERROR, "PyList_SetItem() failed, while setting up arguments"); if (proc->argnames && proc->argnames[i] && PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1) PLy_elog(ERROR, "PyDict_SetItemString() failed, while setting up arguments"); arg = NULL; } /* Set up output conversion for functions returning RECORD */ if (proc->result.out.d.typoid == RECORDOID) { TupleDesc desc; if (get_call_result_type(fcinfo, NULL, &desc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); /* cache the output conversion functions */ PLy_output_record_funcs(&(proc->result), desc); } } PG_CATCH(); { Py_XDECREF(arg); Py_XDECREF(args); PG_RE_THROW(); } PG_END_TRY(); return args; }
/* trigger subhandler * * the python function is expected to return Py_None if the tuple is * acceptable and unmodified. Otherwise it should return a PyString * object who's value is SKIP, or MODIFY. SKIP means don't perform * this action. MODIFY means the tuple has been modified, so update * tuple and perform action. SKIP and MODIFY assume the trigger fires * BEFORE the event and is ROW level. postgres expects the function * to take no arguments and return an argument of type trigger. */ HeapTuple PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc) { HeapTuple rv = NULL; PyObject *volatile plargs = NULL; PyObject *volatile plrv = NULL; TriggerData *tdata; Assert(CALLED_AS_TRIGGER(fcinfo)); /* * Input/output conversion for trigger tuples. Use the result TypeInfo * variable to store the tuple conversion info. We do this over again on * each call to cover the possibility that the relation's tupdesc changed * since the trigger was last called. PLy_input_tuple_funcs and * PLy_output_tuple_funcs are responsible for not doing repetitive work. */ tdata = (TriggerData *) fcinfo->context; PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); PG_TRY(); { plargs = PLy_trigger_build_args(fcinfo, proc, &rv); plrv = PLy_procedure_call(proc, "TD", plargs); Assert(plrv != NULL); /* * Disconnect from SPI manager */ if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); /* * return of None means we're happy with the tuple */ if (plrv != Py_None) { char *srv; if (PyString_Check(plrv)) srv = PyString_AsString(plrv); else if (PyUnicode_Check(plrv)) srv = PLyUnicode_AsString(plrv); else { ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("unexpected return value from trigger procedure"), errdetail("Expected None or a string."))); srv = NULL; /* keep compiler quiet */ } if (pg_strcasecmp(srv, "SKIP") == 0) rv = NULL; else if (pg_strcasecmp(srv, "MODIFY") == 0) { TriggerData *tdata = (TriggerData *) fcinfo->context; if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event) || TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) rv = PLy_modify_tuple(proc, plargs, tdata, rv); else ereport(WARNING, (errmsg("PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"))); } else if (pg_strcasecmp(srv, "OK") != 0) { /* * accept "OK" as an alternative to None; otherwise, raise an * error */ ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("unexpected return value from trigger procedure"), errdetail("Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."))); } } } PG_CATCH(); { Py_XDECREF(plargs); Py_XDECREF(plrv); PG_RE_THROW(); } PG_END_TRY(); Py_DECREF(plargs); Py_DECREF(plrv); return rv; }
Datum xpath_table(PG_FUNCTION_ARGS) { /* Function parameters */ char *pkeyfield = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *xmlfield = text_to_cstring(PG_GETARG_TEXT_PP(1)); char *relname = text_to_cstring(PG_GETARG_TEXT_PP(2)); char *xpathset = text_to_cstring(PG_GETARG_TEXT_PP(3)); char *condition = text_to_cstring(PG_GETARG_TEXT_PP(4)); /* SPI (input tuple) support */ SPITupleTable *tuptable; HeapTuple spi_tuple; TupleDesc spi_tupdesc; /* Output tuple (tuplestore) support */ Tuplestorestate *tupstore = NULL; TupleDesc ret_tupdesc; HeapTuple ret_tuple; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; AttInMetadata *attinmeta; MemoryContext per_query_ctx; MemoryContext oldcontext; char **values; xmlChar **xpaths; char *pos; const char *pathsep = "|"; int numpaths; int ret; int proc; int i; int j; int rownr; /* For issuing multiple rows from one original * document */ bool had_values; /* To determine end of nodeset results */ StringInfoData query_buf; PgXmlErrorContext *xmlerrcxt; volatile xmlDocPtr doctree = NULL; /* We only have a valid tuple description in table function mode */ 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->expectedDesc == NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("xpath_table must be called as a table function"))); /* * We want to materialise because it means that we don't have to carry * libxml2 parser state between invocations of this function */ if (!(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("xpath_table requires Materialize mode, but it is not " "allowed in this context"))); /* * The tuplestore must exist in a higher context than this function call * (per_query_ctx is used) */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); /* * Create the tuplestore - work_mem is the max in-memory size before a * file is created on disk to hold it. */ tupstore = tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random, false, work_mem); MemoryContextSwitchTo(oldcontext); /* get the requested return tuple description */ ret_tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); /* must have at least one output column (for the pkey) */ if (ret_tupdesc->natts < 1) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("xpath_table must have at least one output column"))); /* * At the moment we assume that the returned attributes make sense for the * XPath specififed (i.e. we trust the caller). It's not fatal if they get * it wrong - the input function for the column type will raise an error * if the path result can't be converted into the correct binary * representation. */ attinmeta = TupleDescGetAttInMetadata(ret_tupdesc); /* Set return mode and allocate value space. */ rsinfo->returnMode = SFRM_Materialize; rsinfo->setDesc = ret_tupdesc; values = (char **) palloc(ret_tupdesc->natts * sizeof(char *)); xpaths = (xmlChar **) palloc(ret_tupdesc->natts * sizeof(xmlChar *)); /* * Split XPaths. xpathset is a writable CString. * * Note that we stop splitting once we've done all needed for tupdesc */ numpaths = 0; pos = xpathset; while (numpaths < (ret_tupdesc->natts - 1)) { xpaths[numpaths++] = (xmlChar *) pos; pos = strstr(pos, pathsep); if (pos != NULL) { *pos = '\0'; pos++; } else break; } /* Now build query */ initStringInfo(&query_buf); /* Build initial sql statement */ appendStringInfo(&query_buf, "SELECT %s, %s FROM %s WHERE %s", pkeyfield, xmlfield, relname, condition); if ((ret = SPI_connect()) < 0) elog(ERROR, "xpath_table: SPI_connect returned %d", ret); if ((ret = SPI_exec(query_buf.data, 0)) != SPI_OK_SELECT) elog(ERROR, "xpath_table: SPI execution failed for query %s", query_buf.data); proc = SPI_processed; /* elog(DEBUG1,"xpath_table: SPI returned %d rows",proc); */ tuptable = SPI_tuptable; spi_tupdesc = tuptable->tupdesc; /* Switch out of SPI context */ MemoryContextSwitchTo(oldcontext); /* * Check that SPI returned correct result. If you put a comma into one of * the function parameters, this will catch it when the SPI query returns * e.g. 3 columns. */ if (spi_tupdesc->natts != 2) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("expression returning multiple columns is not valid in parameter list"), errdetail("Expected two columns in SPI result, got %d.", spi_tupdesc->natts))); } /* * Setup the parser. This should happen after we are done evaluating the * query, in case it calls functions that set up libxml differently. */ xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY); PG_TRY(); { /* For each row i.e. document returned from SPI */ for (i = 0; i < proc; i++) { char *pkey; char *xmldoc; xmlXPathContextPtr ctxt; xmlXPathObjectPtr res; xmlChar *resstr; xmlXPathCompExprPtr comppath; /* Extract the row data as C Strings */ spi_tuple = tuptable->vals[i]; pkey = SPI_getvalue(spi_tuple, spi_tupdesc, 1); xmldoc = SPI_getvalue(spi_tuple, spi_tupdesc, 2); /* * Clear the values array, so that not-well-formed documents * return NULL in all columns. Note that this also means that * spare columns will be NULL. */ for (j = 0; j < ret_tupdesc->natts; j++) values[j] = NULL; /* Insert primary key */ values[0] = pkey; /* Parse the document */ if (xmldoc) doctree = xmlParseMemory(xmldoc, strlen(xmldoc)); else /* treat NULL as not well-formed */ doctree = NULL; if (doctree == NULL) { /* not well-formed, so output all-NULL tuple */ ret_tuple = BuildTupleFromCStrings(attinmeta, values); tuplestore_puttuple(tupstore, ret_tuple); heap_freetuple(ret_tuple); } else { /* New loop here - we have to deal with nodeset results */ rownr = 0; do { /* Now evaluate the set of xpaths. */ had_values = false; for (j = 0; j < numpaths; j++) { ctxt = xmlXPathNewContext(doctree); ctxt->node = xmlDocGetRootElement(doctree); /* compile the path */ comppath = xmlXPathCompile(xpaths[j]); if (comppath == NULL) xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, "XPath Syntax Error"); /* Now evaluate the path expression. */ res = xmlXPathCompiledEval(comppath, ctxt); xmlXPathFreeCompExpr(comppath); if (res != NULL) { switch (res->type) { case XPATH_NODESET: /* We see if this nodeset has enough nodes */ if (res->nodesetval != NULL && rownr < res->nodesetval->nodeNr) { resstr = xmlXPathCastNodeToString(res->nodesetval->nodeTab[rownr]); had_values = true; } else resstr = NULL; break; case XPATH_STRING: resstr = xmlStrdup(res->stringval); break; default: elog(NOTICE, "unsupported XQuery result: %d", res->type); resstr = xmlStrdup((const xmlChar *) "<unsupported/>"); } /* * Insert this into the appropriate column in the * result tuple. */ values[j + 1] = (char *) resstr; } xmlXPathFreeContext(ctxt); } /* Now add the tuple to the output, if there is one. */ if (had_values) { ret_tuple = BuildTupleFromCStrings(attinmeta, values); tuplestore_puttuple(tupstore, ret_tuple); heap_freetuple(ret_tuple); } rownr++; } while (had_values); } if (doctree != NULL) xmlFreeDoc(doctree); doctree = NULL; if (pkey) pfree(pkey); if (xmldoc) pfree(xmldoc); } } PG_CATCH(); { if (doctree != NULL) xmlFreeDoc(doctree); pg_xml_done(xmlerrcxt, true); PG_RE_THROW(); } PG_END_TRY(); if (doctree != NULL) xmlFreeDoc(doctree); pg_xml_done(xmlerrcxt, false); tuplestore_donestoring(tupstore); SPI_finish(); rsinfo->setResult = tupstore; /* * SFRM_Materialize mode expects us to return a NULL Datum. The actual * tuples are in our tuplestore and passed back through rsinfo->setResult. * rsinfo->setDesc is set to the tuple description that we actually used * to build our tuples with, so the caller can verify we did what it was * expecting. */ return (Datum) 0; }
/* * plr_SPI_execp - The builtin SPI_execp command for the R interpreter */ SEXP plr_SPI_execp(SEXP rsaved_plan, SEXP rargvalues) { saved_plan_desc *plan_desc = (saved_plan_desc *) R_ExternalPtrAddr(rsaved_plan); void *saved_plan = plan_desc->saved_plan; int nargs = plan_desc->nargs; Oid *typeids = plan_desc->typeids; Oid *typelems = plan_desc->typelems; FmgrInfo *typinfuncs = plan_desc->typinfuncs; int i; Datum *argvalues = NULL; char *nulls = NULL; bool isnull = false; SEXP obj; int spi_rc = 0; char buf[64]; int count = 0; int ntuples; SEXP result = NULL; MemoryContext oldcontext; PREPARE_PG_TRY; /* set up error context */ PUSH_PLERRCONTEXT(rsupport_error_callback, "pg.spi.execp"); if (nargs > 0) { if (!Rf_isVectorList(rargvalues)) error("%s", "second parameter must be a list of arguments " \ "to the prepared plan"); if (length(rargvalues) != nargs) error("list of arguments (%d) is not the same length " \ "as that of the prepared plan (%d)", length(rargvalues), nargs); argvalues = (Datum *) palloc(nargs * sizeof(Datum)); nulls = (char *) palloc(nargs * sizeof(char)); } for (i = 0; i < nargs; i++) { PROTECT(obj = VECTOR_ELT(rargvalues, i)); argvalues[i] = get_datum(obj, typeids[i], typelems[i], typinfuncs[i], &isnull); if (!isnull) nulls[i] = ' '; else nulls[i] = 'n'; UNPROTECT(1); } /* switch to SPI memory context */ SWITCHTO_PLR_SPI_CONTEXT(oldcontext); /* * trap elog/ereport so we can let R finish up gracefully * and generate the error once we exit the interpreter */ PG_TRY(); { /* Execute the plan */ spi_rc = SPI_execp(saved_plan, argvalues, nulls, count); } PLR_PG_CATCH(); PLR_PG_END_TRY(); /* back to caller's memory context */ MemoryContextSwitchTo(oldcontext); /* check the result */ switch (spi_rc) { case SPI_OK_UTILITY: snprintf(buf, sizeof(buf), "%d", 0); SPI_freetuptable(SPI_tuptable); PROTECT(result = NEW_CHARACTER(1)); SET_STRING_ELT(result, 0, COPY_TO_USER_STRING(buf)); UNPROTECT(1); break; case SPI_OK_SELINTO: case SPI_OK_INSERT: case SPI_OK_DELETE: case SPI_OK_UPDATE: snprintf(buf, sizeof(buf), "%d", SPI_processed); SPI_freetuptable(SPI_tuptable); PROTECT(result = NEW_CHARACTER(1)); SET_STRING_ELT(result, 0, COPY_TO_USER_STRING(buf)); UNPROTECT(1); break; case SPI_OK_SELECT: ntuples = SPI_processed; if (ntuples > 0) { result = rpgsql_get_results(ntuples, SPI_tuptable); SPI_freetuptable(SPI_tuptable); } else result = R_NilValue; break; case SPI_ERROR_ARGUMENT: error("SPI_execp() failed: SPI_ERROR_ARGUMENT"); break; case SPI_ERROR_UNCONNECTED: error("SPI_execp() failed: SPI_ERROR_UNCONNECTED"); break; case SPI_ERROR_COPY: error("SPI_execp() failed: SPI_ERROR_COPY"); break; case SPI_ERROR_CURSOR: error("SPI_execp() failed: SPI_ERROR_CURSOR"); break; case SPI_ERROR_TRANSACTION: error("SPI_execp() failed: SPI_ERROR_TRANSACTION"); break; case SPI_ERROR_OPUNKNOWN: error("SPI_execp() failed: SPI_ERROR_OPUNKNOWN"); break; default: error("SPI_execp() failed: %d", spi_rc); break; } POP_PLERRCONTEXT; return result; }
/* * initTrie - create trie from file. * * Function converts UTF8-encoded file into current encoding. */ static TrieChar * initTrie(const char *filename) { TrieChar *volatile rootTrie = NULL; MemoryContext ccxt = CurrentMemoryContext; tsearch_readline_state trst; volatile bool skip; filename = get_tsearch_config_filename(filename, "rules"); if (!tsearch_readline_begin(&trst, filename)) ereport(ERROR, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("could not open unaccent file \"%s\": %m", filename))); do { /* * pg_do_encoding_conversion() (called by tsearch_readline()) will * emit exception if it finds untranslatable characters in current * locale. We just skip such lines, continuing with the next. */ skip = true; PG_TRY(); { char *line; while ((line = tsearch_readline(&trst)) != NULL) { /*---------- * The format of each line must be "src" or "src trg", where * src and trg are sequences of one or more non-whitespace * characters, separated by whitespace. Whitespace at start * or end of line is ignored. If trg is omitted, an empty * string is used as the replacement. * * We use a simple state machine, with states * 0 initial (before src) * 1 in src * 2 in whitespace after src * 3 in trg * 4 in whitespace after trg * -1 syntax error detected *---------- */ int state; char *ptr; char *src = NULL; char *trg = NULL; int ptrlen; int srclen = 0; int trglen = 0; state = 0; for (ptr = line; *ptr; ptr += ptrlen) { ptrlen = pg_mblen(ptr); /* ignore whitespace, but end src or trg */ if (t_isspace(ptr)) { if (state == 1) state = 2; else if (state == 3) state = 4; continue; } switch (state) { case 0: /* start of src */ src = ptr; srclen = ptrlen; state = 1; break; case 1: /* continue src */ srclen += ptrlen; break; case 2: /* start of trg */ trg = ptr; trglen = ptrlen; state = 3; break; case 3: /* continue trg */ trglen += ptrlen; break; default: /* bogus line format */ state = -1; break; } } if (state == 1 || state == 2) { /* trg was omitted, so use "" */ trg = ""; trglen = 0; } if (state > 0) rootTrie = placeChar(rootTrie, (unsigned char *) src, srclen, trg, trglen); else if (state < 0) ereport(WARNING, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("invalid syntax: more than two strings in unaccent rule"))); pfree(line); } skip = false; } PG_CATCH(); { ErrorData *errdata; MemoryContext ecxt; ecxt = MemoryContextSwitchTo(ccxt); errdata = CopyErrorData(); if (errdata->sqlerrcode == ERRCODE_UNTRANSLATABLE_CHARACTER) { FlushErrorState(); } else { MemoryContextSwitchTo(ecxt); PG_RE_THROW(); } } PG_END_TRY(); } while (skip); tsearch_readline_end(&trst); return rootTrie; }
/* * Takes the prepared plan rsaved_plan and creates a cursor * for it using the values specified in ragvalues. * */ SEXP plr_SPI_cursor_open(SEXP cursor_name_arg,SEXP rsaved_plan, SEXP rargvalues) { saved_plan_desc *plan_desc = (saved_plan_desc *) R_ExternalPtrAddr(rsaved_plan); void *saved_plan = plan_desc->saved_plan; int nargs = plan_desc->nargs; Oid *typeids = plan_desc->typeids; FmgrInfo *typinfuncs = plan_desc->typinfuncs; int i; Datum *argvalues = NULL; char *nulls = NULL; bool isnull = false; SEXP obj; SEXP result = NULL; MemoryContext oldcontext; char cursor_name[64]; Portal portal=NULL; PREPARE_PG_TRY; PUSH_PLERRCONTEXT(rsupport_error_callback, "pg.spi.cursor_open"); /* Divide rargvalues */ if (nargs > 0) { if (!Rf_isVectorList(rargvalues)) error("%s", "second parameter must be a list of arguments " \ "to the prepared plan"); if (length(rargvalues) != nargs) error("list of arguments (%d) is not the same length " \ "as that of the prepared plan (%d)", length(rargvalues), nargs); argvalues = (Datum *) palloc(nargs * sizeof(Datum)); nulls = (char *) palloc(nargs * sizeof(char)); } for (i = 0; i < nargs; i++) { PROTECT(obj = VECTOR_ELT(rargvalues, i)); argvalues[i] = get_scalar_datum(obj, typeids[i], typinfuncs[i], &isnull); if (!isnull) nulls[i] = ' '; else nulls[i] = 'n'; UNPROTECT(1); } strncpy(cursor_name, CHAR(STRING_ELT(cursor_name_arg,0)), 64); /* switch to SPI memory context */ SWITCHTO_PLR_SPI_CONTEXT(oldcontext); /* * trap elog/ereport so we can let R finish up gracefully * and generate the error once we exit the interpreter */ PG_TRY(); { /* Open the cursor */ portal = SPI_cursor_open(cursor_name,saved_plan, argvalues, nulls,1); } PLR_PG_CATCH(); PLR_PG_END_TRY(); /* back to caller's memory context */ MemoryContextSwitchTo(oldcontext); if(portal==NULL) error("SPI_cursor_open() failed"); else result = R_MakeExternalPtr(portal, R_NilValue, R_NilValue); POP_PLERRCONTEXT; return result; }
/* * Read pooler protocol message from the buffer. */ int pool_getmessage(PoolPort *port, StringInfo s, int maxlen) { int32 len; resetStringInfo(s); /* Read message length word */ if (pool_getbytes(port, (char *) &len, 4) == EOF) { ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("unexpected EOF within message length word"))); return EOF; } len = ntohl(len); if (len < 4 || (maxlen > 0 && len > maxlen)) { ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("invalid message length"))); return EOF; } len -= 4; /* discount length itself */ if (len > 0) { /* * Allocate space for message. If we run out of room (ridiculously * large message), we will elog(ERROR) */ PG_TRY(); { enlargeStringInfo(s, len); } PG_CATCH(); { if (pool_discardbytes(port, len) == EOF) ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete message from client"))); PG_RE_THROW(); } PG_END_TRY(); /* And grab the message */ if (pool_getbytes(port, s->data, len) == EOF) { ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete message from client"))); return EOF; } s->len = len; /* Place a trailing null per StringInfo convention */ s->data[len] = '\0'; } return 0; }
/* * Assign hook routine for "gp_role" option. This variable has context * PGC_SUSET so that is can only be set by a superuser via the SET command. * (It can also be set using an option on postmaster start, but this isn't * interesting beccause the derived global CdbRole is always set (along with * CdbSessionRole) on backend startup for a new connection. * * See src/backend/util/misc/guc.c for option definition. */ const char * assign_gp_role(const char *newval, bool doit, GucSource source) { #if FALSE elog(DEBUG1, "assign_gp_role: gp_role=%s, newval=%s, doit=%s", show_gp_role(), newval, (doit ? "true" : "false")); #endif GpRoleValue newrole = string_to_role(newval); GpRoleValue oldrole = Gp_role; if (newrole == GP_ROLE_UNDEFINED) { return NULL; } if (doit) { /* * When changing between roles, we must call cdb_cleanup and then * cdb_setup to get setup and connections appropriate to the new role. */ bool do_disconnect = false; bool do_connect = false; if (Gp_role != newrole && IsUnderPostmaster) { if (Gp_role != GP_ROLE_UTILITY) do_disconnect = true; if (newrole != GP_ROLE_UTILITY) do_connect = true; } if (do_disconnect) cdb_cleanup(0, 0); Gp_role = newrole; if (source != PGC_S_DEFAULT) { if (do_connect) { /* * In case there are problems with the Greenplum Database * tables or data, we catch any error coming out of * cdblink_setup so we can set the gp_role back to what it * was. Otherwise we may be left with inappropriate * connections for the new role. */ PG_TRY(); { cdb_setup(); } PG_CATCH(); { cdb_cleanup(0, 0); Gp_role = oldrole; if (Gp_role != GP_ROLE_UTILITY) cdb_setup(); PG_RE_THROW(); } PG_END_TRY(); } } } return newval; }
/* * Connect to remote server using specified server and user mapping properties. */ static Jconn * connect_jdbc_server(ForeignServer *server, UserMapping *user) { Jconn *volatile conn = NULL; /* * Use PG_TRY block to ensure closing connection on error. */ PG_TRY(); { const char **keywords; const char **values; int n; /* * Construct connection params from generic options of ForeignServer * and UserMapping. (Some of them might not be libpq options, in * which case we'll just waste a few array slots.) Add 3 extra slots * for fallback_application_name, client_encoding, end marker. */ n = list_length(server->options) + list_length(user->options) + 3; keywords = (const char **) palloc(n * sizeof(char *)); values = (const char **) palloc(n * sizeof(char *)); n = 0; n += ExtractConnectionOptions(server->options, keywords + n, values + n); n += ExtractConnectionOptions(user->options, keywords + n, values + n); /* Use "jdbc2_fdw" as fallback_application_name. */ keywords[n] = "fallback_application_name"; values[n] = "jdbc2_fdw"; n++; /* Set client_encoding so that libpq can convert encoding properly. */ keywords[n] = "client_encoding"; values[n] = GetDatabaseEncodingName(); n++; keywords[n] = values[n] = NULL; /* verify connection parameters and make connection */ check_conn_params(keywords, values); conn = JQconnectdbParams(server, user, keywords, values); if (!conn || JQstatus(conn) != CONNECTION_OK) { char *connmessage; int msglen; /* libpq typically appends a newline, strip that */ connmessage = pstrdup(JQerrorMessage(conn)); msglen = strlen(connmessage); if (msglen > 0 && connmessage[msglen - 1] == '\n') connmessage[msglen - 1] = '\0'; ereport(ERROR, (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), errmsg("could not connect to server \"%s\"", server->servername), errdetail_internal("%s", connmessage))); } /* * Check that non-superuser has used password to establish connection; * otherwise, he's piggybacking on the jdbc server's user * identity. See also dblink_security_check() in contrib/dblink. */ if (!superuser() && !JQconnectionUsedPassword(conn)) ereport(ERROR, (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED), errmsg("password is required"), errdetail("Non-superuser cannot connect if the server does not request a password."), errhint("Target server's authentication method must be changed."))); pfree(keywords); pfree(values); } PG_CATCH(); { /* Release Jconn data structure if we managed to create one */ if (conn) JQfinish(conn); PG_RE_THROW(); } PG_END_TRY(); return conn; }
/* -------------------------------- * pq_getmessage - get a message with length word from connection * * The return value is placed in an expansible StringInfo, which has * already been initialized by the caller. * Only the message body is placed in the StringInfo; the length word * is removed. Also, s->cursor is initialized to zero for convenience * in scanning the message contents. * * If maxlen is not zero, it is an upper limit on the length of the * message we are willing to accept. We abort the connection (by * returning EOF) if client tries to send more than that. * * returns 0 if OK, EOF if trouble * -------------------------------- */ int pq_getmessage(StringInfo s, int maxlen) { int32 len; /* Reset message buffer to empty */ s->len = 0; s->data[0] = '\0'; s->cursor = 0; /* Read message length word */ if (pq_getbytes((char *) &len, 4) == EOF) { ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("unexpected EOF within message length word"))); return EOF; } len = ntohl(len); if (len < 4 || (maxlen > 0 && len > maxlen)) { ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("invalid message length"))); return EOF; } len -= 4; /* discount length itself */ if (len > 0) { /* * Allocate space for message. If we run out of room (ridiculously * large message), we will elog(ERROR), but we want to discard the * message body so as not to lose communication sync. */ PG_TRY(); { enlargeStringInfo(s, len); } PG_CATCH(); { if (pq_discardbytes(len) == EOF) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete message from client"))); PG_RE_THROW(); } PG_END_TRY(); /* And grab the message */ if (pq_getbytes(s->data, len) == EOF) { ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete message from client"))); return EOF; } s->len = len; /* Place a trailing null per StringInfo convention */ s->data[len] = '\0'; } return 0; }
/* * Extract a Python traceback from the given exception data. * * The exception error message is returned in xmsg, the traceback in * tbmsg (both as palloc'd strings) and the traceback depth in * tb_depth. * * We release refcounts on all the Python objects in the traceback stack, * but not on e or v. */ static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, char **xmsg, char **tbmsg, int *tb_depth) { PyObject *e_type_o; PyObject *e_module_o; char *e_type_s = NULL; char *e_module_s = NULL; PyObject *vob = NULL; char *vstr; StringInfoData xstr; StringInfoData tbstr; /* * if no exception, return nulls */ if (e == NULL) { *xmsg = NULL; *tbmsg = NULL; *tb_depth = 0; return; } /* * Format the exception and its value and put it in xmsg. */ e_type_o = PyObject_GetAttrString(e, "__name__"); e_module_o = PyObject_GetAttrString(e, "__module__"); if (e_type_o) e_type_s = PyString_AsString(e_type_o); if (e_type_s) e_module_s = PyString_AsString(e_module_o); if (v && ((vob = PyObject_Str(v)) != NULL)) vstr = PyString_AsString(vob); else vstr = "unknown"; initStringInfo(&xstr); if (!e_type_s || !e_module_s) { if (PyString_Check(e)) /* deprecated string exceptions */ appendStringInfoString(&xstr, PyString_AsString(e)); else /* shouldn't happen */ appendStringInfoString(&xstr, "unrecognized exception"); } /* mimics behavior of traceback.format_exception_only */ else if (strcmp(e_module_s, "builtins") == 0 || strcmp(e_module_s, "__main__") == 0 || strcmp(e_module_s, "exceptions") == 0) appendStringInfo(&xstr, "%s", e_type_s); else appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); appendStringInfo(&xstr, ": %s", vstr); *xmsg = xstr.data; /* * Now format the traceback and put it in tbmsg. */ *tb_depth = 0; initStringInfo(&tbstr); /* Mimick Python traceback reporting as close as possible. */ appendStringInfoString(&tbstr, "Traceback (most recent call last):"); while (tb != NULL && tb != Py_None) { PyObject *volatile tb_prev = NULL; PyObject *volatile frame = NULL; PyObject *volatile code = NULL; PyObject *volatile name = NULL; PyObject *volatile lineno = NULL; PyObject *volatile filename = NULL; PG_TRY(); { /* * Ancient versions of Python (circa 2.3) contain a bug whereby * the fetches below can fail if the error indicator is set. */ PyErr_Clear(); lineno = PyObject_GetAttrString(tb, "tb_lineno"); if (lineno == NULL) elog(ERROR, "could not get line number from Python traceback"); frame = PyObject_GetAttrString(tb, "tb_frame"); if (frame == NULL) elog(ERROR, "could not get frame from Python traceback"); code = PyObject_GetAttrString(frame, "f_code"); if (code == NULL) elog(ERROR, "could not get code object from Python frame"); name = PyObject_GetAttrString(code, "co_name"); if (name == NULL) elog(ERROR, "could not get function name from Python code object"); filename = PyObject_GetAttrString(code, "co_filename"); if (filename == NULL) elog(ERROR, "could not get file name from Python code object"); } PG_CATCH(); { Py_XDECREF(frame); Py_XDECREF(code); Py_XDECREF(name); Py_XDECREF(lineno); Py_XDECREF(filename); PG_RE_THROW(); } PG_END_TRY(); /* The first frame always points at <module>, skip it. */ if (*tb_depth > 0) { PLyExecutionContext *exec_ctx = PLy_current_execution_context(); char *proname; char *fname; char *line; char *plain_filename; long plain_lineno; /* * The second frame points at the internal function, but to mimick * Python error reporting we want to say <module>. */ if (*tb_depth == 1) fname = "<module>"; else fname = PyString_AsString(name); proname = PLy_procedure_name(exec_ctx->curr_proc); plain_filename = PyString_AsString(filename); plain_lineno = PyInt_AsLong(lineno); if (proname == NULL) appendStringInfo( &tbstr, "\n PL/Python anonymous code block, line %ld, in %s", plain_lineno - 1, fname); else appendStringInfo( &tbstr, "\n PL/Python function \"%s\", line %ld, in %s", proname, plain_lineno - 1, fname); /* * function code object was compiled with "<string>" as the * filename */ if (exec_ctx->curr_proc && plain_filename != NULL && strcmp(plain_filename, "<string>") == 0) { /* * If we know the current procedure, append the exact line * from the source, again mimicking Python's traceback.py * module behavior. We could store the already line-split * source to avoid splitting it every time, but producing a * traceback is not the most important scenario to optimize * for. But we do not go as far as traceback.py in reading * the source of imported modules. */ line = get_source_line(exec_ctx->curr_proc->src, plain_lineno); if (line) { appendStringInfo(&tbstr, "\n %s", line); pfree(line); } } } Py_DECREF(frame); Py_DECREF(code); Py_DECREF(name); Py_DECREF(lineno); Py_DECREF(filename); /* Release the current frame and go to the next one. */ tb_prev = tb; tb = PyObject_GetAttrString(tb, "tb_next"); Assert(tb_prev != Py_None); Py_DECREF(tb_prev); if (tb == NULL) elog(ERROR, "could not traverse Python traceback"); (*tb_depth)++; } /* Return the traceback. */ *tbmsg = tbstr.data; Py_XDECREF(e_type_o); Py_XDECREF(e_module_o); Py_XDECREF(vob); }
static int64 get_size_from_segDBs(const char * cmd) { int spiresult; bool succeeded = false; int64 result = 0; volatile bool connected = false; Assert(Gp_role == GP_ROLE_DISPATCH); PG_TRY(); { HeapTuple tup; TupleDesc tupdesc; bool isnull; Datum size; do { /* Establish an SPI session as a client of myself. */ if (SPI_connect() != SPI_OK_CONNECT) break; connected = true; /* Do the query. */ spiresult = SPI_execute(cmd, false, 0); /* Did the query succeed? */ if (spiresult != SPI_OK_SELECT) break; if (SPI_processed < 1) break; tup = SPI_tuptable->vals[0]; tupdesc = SPI_tuptable->tupdesc; size = heap_getattr(SPI_tuptable->vals[0], 1, SPI_tuptable->tupdesc, &isnull); if (isnull) break; result = DatumGetInt64(size); succeeded = true; } while (0); /* End recursive session. */ connected = false; SPI_finish(); if (!succeeded) elog(ERROR, "Unable to get sizes from segments"); } /* Clean up in case of error. */ PG_CATCH(); { /* End recursive session. */ if (connected) SPI_finish(); /* Carry on with error handling. */ PG_RE_THROW(); } PG_END_TRY(); return result; }
/* * ExecWorkFile_Write * write the given data from the end of the last write position. * * This function returns true if the write succeeds. Otherwise, return false. */ bool ExecWorkFile_Write(ExecWorkFile *workfile, void *data, uint64 size) { Assert(workfile != NULL); uint64 bytes; if (data == NULL || size == 0) { return false; } /* Test the per-query and per-segment limit */ if ((workfile->flags & EXEC_WORKFILE_LIMIT_SIZE) && !WorkfileDiskspace_Reserve(size)) { /* Failed to reserve additional disk space, notify caller */ workfile_mgr_report_error(); } switch(workfile->fileType) { case BUFFILE: {} BufFile *buffile = (BufFile *)workfile->file; int64 current_size = BufFileGetSize(buffile); int64 new_size = 0; PG_TRY(); { bytes = BufFileWrite(buffile, data, size); } PG_CATCH(); { new_size = BufFileGetSize(buffile); workfile->size = new_size; WorkfileDiskspace_Commit( (new_size - current_size), size, true /* update_query_size */); int64 size_evicted = workfile_mgr_evict(MIN_EVICT_SIZE); elog(gp_workfile_caching_loglevel, "Hit out of disk space, evicted " INT64_FORMAT " bytes", size_evicted); PG_RE_THROW(); } PG_END_TRY(); new_size = BufFileGetSize(buffile); workfile->size = new_size; WorkfileDiskspace_Commit( (new_size - current_size), size, true /* update_query_size */); workfile_update_in_progress_size(workfile, new_size - current_size); if (bytes != size) { workfile_mgr_report_error(); } break; case BFZ: PG_TRY(); { bfz_append((bfz_t *)workfile->file, data, size); } PG_CATCH(); { Assert(WorkfileDiskspace_IsFull()); WorkfileDiskspace_Commit(0, size, true /* update_query_size */); int64 size_evicted = workfile_mgr_evict(MIN_EVICT_SIZE); elog(gp_workfile_caching_loglevel, "Hit out of disk space, evicted " INT64_FORMAT " bytes", size_evicted); PG_RE_THROW(); } PG_END_TRY(); /* bfz_append always adds to the file size */ workfile->size += size; if ((workfile->flags & EXEC_WORKFILE_LIMIT_SIZE)) { WorkfileDiskspace_Commit(size, size, true /* update_query_size */); } workfile_update_in_progress_size(workfile, size); break; default: insist_log(false, "invalid work file type: %d", workfile->fileType); } return true; }
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; }
/* * Workfile-manager specific function to clean up before releasing a * workfile set from the cache. * */ static void workfile_mgr_cleanup_set(const void *resource) { workfile_set *work_set = (workfile_set *) resource; /* * We have to make this callback function return cleanly ALL the * time. It shouldn't throw an exception. * We must try to clean up as much as we can in the callback, and * then never be called again. * This means holding interrupts, catching and handling all exceptions. */ if (work_set->on_disk) { ereport(gp_workfile_caching_loglevel, (errmsg("workfile mgr cleanup deleting set: key=0x%0xd, size=" INT64_FORMAT " in_progress_size=" INT64_FORMAT " path=%s", work_set->key, work_set->size, work_set->in_progress_size, work_set->path), errprintstack(true))); Assert(NULL == work_set->set_plan); PG_TRY(); { #ifdef FAULT_INJECTOR FaultInjector_InjectFaultIfSet( WorkfileCleanupSet, DDLNotSpecified, "", /* databaseName */ "" /* tableName */ ); #endif /* Prevent interrupts while cleaning up */ HOLD_INTERRUPTS(); workfile_mgr_delete_set_directory(work_set->path); /* Now we can allow interrupts again */ RESUME_INTERRUPTS(); } PG_CATCH(); { elog(LOG, "Cleaning up workfile set directory path=%s failed. Proceeding", work_set->path); /* We're not re-throwing the error. Otherwise we'll end up having * to clean up again, probably failing again. */ } PG_END_TRY(); /* * The most accurate size of a workset is recorded in work_set->in_progress_size. * work_set->size is only updated when we close a file, so it lags behind */ Assert(work_set->in_progress_size >= work_set->size); int64 size_to_delete = work_set->in_progress_size; elog(gp_workfile_caching_loglevel, "Subtracting " INT64_FORMAT " from workfile diskspace", size_to_delete); /* * When subtracting the size of this workset from our accounting, * only update the per-query counter if we created the workset. * In that case, the state is ACQUIRED, otherwise is CACHED or DELETED */ CacheEntry *cacheEntry = CACHE_ENTRY_HEADER(resource); bool update_query_space = (cacheEntry->state == CACHE_ENTRY_ACQUIRED); WorkfileDiskspace_Commit(0, size_to_delete, update_query_space); } else { /* Non-physical workfile set, we need to free up the plan memory */ if (NULL != work_set->set_plan->serialized_plan) { pfree(work_set->set_plan->serialized_plan); } if (NULL != work_set->set_plan) { pfree(work_set->set_plan); } } }
static PyObject * PLy_cursor_fetch(PyObject *self, PyObject *args) { PLyCursorObject *cursor; int count; PLyResultObject *ret; volatile MemoryContext oldcontext; volatile ResourceOwner oldowner; Portal portal; if (!PyArg_ParseTuple(args, "i", &count)) return NULL; cursor = (PLyCursorObject *) self; if (cursor->closed) { PLy_exception_set(PyExc_ValueError, "fetch from a closed cursor"); return NULL; } portal = GetPortalByName(cursor->portalname); if (!PortalIsValid(portal)) { PLy_exception_set(PyExc_ValueError, "iterating a cursor in an aborted subtransaction"); return NULL; } ret = (PLyResultObject *) PLy_result_new(); if (ret == NULL) return NULL; oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; PLy_spi_subtransaction_begin(oldcontext, oldowner); PG_TRY(); { SPI_cursor_fetch(portal, true, count); if (cursor->result.is_rowtype != 1) PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc); Py_DECREF(ret->status); ret->status = PyInt_FromLong(SPI_OK_FETCH); Py_DECREF(ret->nrows); ret->nrows = PyInt_FromLong(SPI_processed); if (SPI_processed != 0) { int i; Py_DECREF(ret->rows); ret->rows = PyList_New(SPI_processed); for (i = 0; i < SPI_processed; i++) { PyObject *row = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[i], SPI_tuptable->tupdesc); PyList_SetItem(ret->rows, i, row); } } SPI_freetuptable(SPI_tuptable); PLy_spi_subtransaction_commit(oldcontext, oldowner); } PG_CATCH(); { PLy_spi_subtransaction_abort(oldcontext, oldowner); return NULL; } PG_END_TRY(); return (PyObject *) ret; }
static PyObj statement_first(PyObj self, PyObj args, PyObj kw) { MemoryContext former = CurrentMemoryContext; PyObj c, rob = NULL; if (resolve_parameters(self, &args, &kw)) return(NULL); if (DB_IS_NOT_READY()) return(NULL); if (PyPgStatement_ReturnsRows(self)) { c = PyPgCursor_New(self, args, kw, CUR_ROWS(1)); if (c != NULL) { PyObj r; r = PyIter_Next(c); if (!PyErr_Occurred() && r == NULL) { r = Py_None; Py_INCREF(r); } if (PyPgCursor_Close(c)) { Py_DECREF(c); Py_XDECREF(r); return(NULL); } if (r != NULL) { if (r == Py_None) rob = r; else { Py_ssize_t s = PySequence_Size(r); if (s == -1) rob = NULL; else if (s == 1) { rob = PySequence_GetItem(r, 0); Py_DECREF(r); } else { /* * It has multiple columns, so return the first row. */ rob = r; } } } Py_DECREF(c); } } else { SPIPlanPtr plan; PyObj tdo = PyPgStatement_GetInput(self); TupleDesc td = PyPgTupleDesc_GetTupleDesc(tdo); PyObj pargs; plan = PyPgStatement_GetPlan(self); if (plan == NULL) return(NULL); pargs = Py_NormalizeRow( PyPgTupleDesc_GetNatts(tdo), td, PyPgTupleDesc_GetNameMap(tdo), args ); if (pargs == NULL) return(NULL); PG_TRY(); { int r; Datum *datums; bool *nulls; char *cnulls; int *freemap = PyPgTupleDesc_GetFreeMap(tdo); datums = palloc(sizeof(Datum) * td->natts); nulls = palloc(sizeof(bool) * td->natts); cnulls = palloc(sizeof(char) * td->natts); Py_BuildDatumsAndNulls( td, PyPgTupleDesc_GetTypesTuple(tdo), pargs, datums, nulls ); for (r = 0; r < td->natts; ++r) { cnulls[r] = nulls[r] ? 'n' : ' '; } r = SPI_execute_plan(plan, datums, cnulls, PL_FN_READONLY(), 1); if (r < 0) raise_spi_error(r); rob = PyLong_FromUnsignedLong(SPI_processed); FreeDatumsAndNulls(freemap, datums, nulls); pfree(cnulls); } PG_CATCH(); { PyErr_SetPgError(false); Py_XDECREF(rob); rob = NULL; } PG_END_TRY(); Py_DECREF(pargs); } MemoryContextSwitchTo(former); return(rob); }
static bool ServiceClientPollRead(ServiceClient *serviceClient, void* response, int responseLen, bool *pollResponseReceived) { ServiceConfig *serviceConfig; int n; int saved_err; char *message; bool result = false; DECLARE_SAVE_SUPPRESS_PANIC(); Assert(serviceClient != NULL); serviceConfig = serviceClient->serviceConfig; Assert(serviceConfig != NULL); Assert(response != NULL); PG_TRY(); { SUPPRESS_PANIC(); /* * Attempt to read the response */ while (true) { n = read(serviceClient->sockfd, ((char *)response), responseLen); saved_err = errno; if (n == 0) { ereport(ERROR, (errcode(ERRCODE_GP_INTERCONNECTION_ERROR), errmsg("Connection to '%s' is closed", serviceConfig->title))); } else if (n < 0) { if (saved_err == EWOULDBLOCK) { *pollResponseReceived = false; break; } if (saved_err == EINTR) continue; ereport(ERROR, (errcode(ERRCODE_GP_INTERCONNECTION_ERROR), errmsg("Read error from '%s': %s", serviceConfig->title, strerror(saved_err)))); } if (n != responseLen) { ereport(ERROR, (errcode(ERRCODE_GP_INTERCONNECTION_ERROR), errmsg("Expecting message length %d and actual read length was %d from '%s'", responseLen, n, serviceConfig->title))); return false; } *pollResponseReceived = true; break; } result = true; RESTORE_PANIC(); } PG_CATCH(); { RESTORE_PANIC(); /* Report the error to the server log */ if (!elog_demote(WARNING)) { elog(LOG,"unable to demote error"); PG_RE_THROW(); } message = elog_message(); if (message != NULL && strlen(message) + 1 < sizeof(ClientErrorString)) strcpy(ClientErrorString, message); else strcpy(ClientErrorString, ""); EmitErrorReport(); FlushErrorState(); result = false; } PG_END_TRY(); return result; }