/* * EnumValuesCreate * Create an entry in pg_enum for each of the supplied enum values. * * vals is a list of Value strings. */ void EnumValuesCreate(Oid enumTypeOid, List *vals) { Relation pg_enum; NameData enumlabel; Oid *oids; int elemno, num_elems; Datum values[Natts_pg_enum]; bool nulls[Natts_pg_enum]; ListCell *lc; HeapTuple tup; num_elems = list_length(vals); /* * We do not bother to check the list of values for duplicates --- if * you have any, you'll get a less-than-friendly unique-index violation. * It is probably not worth trying harder. */ pg_enum = heap_open(EnumRelationId, RowExclusiveLock); /* * Allocate OIDs for the enum's members. * * While this method does not absolutely guarantee that we generate no * duplicate OIDs (since we haven't entered each oid into the table * before allocating the next), trouble could only occur if the OID * counter wraps all the way around before we finish. Which seems * unlikely. */ oids = (Oid *) palloc(num_elems * sizeof(Oid)); for (elemno = 0; elemno < num_elems; elemno++) { /* * We assign even-numbered OIDs to all the new enum labels. This * tells the comparison functions the OIDs are in the correct sort * order and can be compared directly. */ Oid new_oid; do { new_oid = GetNewOid(pg_enum); } while (new_oid & 1); oids[elemno] = new_oid; } /* sort them, just in case OID counter wrapped from high to low */ qsort(oids, num_elems, sizeof(Oid), oid_cmp); /* and make the entries */ memset(nulls, false, sizeof(nulls)); elemno = 0; foreach(lc, vals) { char *lab = strVal(lfirst(lc)); /* * labels are stored in a name field, for easier syscache lookup, so * check the length to make sure it's within range. */ if (strlen(lab) > (NAMEDATALEN - 1)) ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("invalid enum label \"%s\"", lab), errdetail("Labels must be %d characters or less.", NAMEDATALEN - 1))); values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid); values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1); namestrcpy(&enumlabel, lab); values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel); tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls); HeapTupleSetOid(tup, oids[elemno]); simple_heap_insert(pg_enum, tup); CatalogUpdateIndexes(pg_enum, tup); heap_freetuple(tup); elemno++; }
void worker_spi_main(Datum main_arg) { int index = DatumGetInt32(main_arg); worktable *table; StringInfoData buf; char name[20]; table = palloc(sizeof(worktable)); sprintf(name, "schema%d", index); table->schema = pstrdup(name); table->name = pstrdup("counted"); /* Establish signal handlers before unblocking signals. */ pqsignal(SIGHUP, worker_spi_sighup); pqsignal(SIGTERM, worker_spi_sigterm); /* We're now ready to receive signals */ BackgroundWorkerUnblockSignals(); /* Connect to our database */ BackgroundWorkerInitializeConnection("postgres", NULL); elog(LOG, "%s initialized with %s.%s", MyBgworkerEntry->bgw_name, table->schema, table->name); initialize_worker_spi(table); /* * Quote identifiers passed to us. Note that this must be done after * initialize_worker_spi, because that routine assumes the names are not * quoted. * * Note some memory might be leaked here. */ table->schema = quote_identifier(table->schema); table->name = quote_identifier(table->name); initStringInfo(&buf); appendStringInfo(&buf, "WITH deleted AS (DELETE " "FROM %s.%s " "WHERE type = 'delta' RETURNING value), " "total AS (SELECT coalesce(sum(value), 0) as sum " "FROM deleted) " "UPDATE %s.%s " "SET value = %s.value + total.sum " "FROM total WHERE type = 'total' " "RETURNING %s.value", table->schema, table->name, table->schema, table->name, table->name, table->name); /* * Main loop: do this until the SIGTERM handler tells us to terminate */ while (!got_sigterm) { int ret; int rc; /* * Background workers mustn't call usleep() or any direct equivalent: * instead, they may wait on their process latch, which sleeps as * necessary, but is awakened if postmaster dies. That way the * background process goes away immediately in an emergency. */ rc = WaitLatch(&MyProc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, worker_spi_naptime * 1000L); ResetLatch(&MyProc->procLatch); /* emergency bailout if postmaster has died */ if (rc & WL_POSTMASTER_DEATH) proc_exit(1); /* * In case of a SIGHUP, just reload the configuration. */ if (got_sighup) { got_sighup = false; ProcessConfigFile(PGC_SIGHUP); } /* * Start a transaction on which we can run queries. Note that each * StartTransactionCommand() call should be preceded by a * SetCurrentStatementStartTimestamp() call, which sets both the time * for the statement we're about the run, and also the transaction * start time. Also, each other query sent to SPI should probably be * preceded by SetCurrentStatementStartTimestamp(), so that statement * start time is always up to date. * * The SPI_connect() call lets us run queries through the SPI manager, * and the PushActiveSnapshot() call creates an "active" snapshot * which is necessary for queries to have MVCC data to work on. * * The pgstat_report_activity() call makes our activity visible * through the pgstat views. */ SetCurrentStatementStartTimestamp(); StartTransactionCommand(); SPI_connect(); PushActiveSnapshot(GetTransactionSnapshot()); pgstat_report_activity(STATE_RUNNING, buf.data); /* We can now execute queries via SPI */ ret = SPI_execute(buf.data, false, 0); if (ret != SPI_OK_UPDATE_RETURNING) elog(FATAL, "cannot select from table %s.%s: error code %d", table->schema, table->name, ret); if (SPI_processed > 0) { bool isnull; int32 val; val = DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull)); if (!isnull) elog(LOG, "%s: count in %s.%s is now %d", MyBgworkerEntry->bgw_name, table->schema, table->name, val); } /* * And finish our transaction. */ SPI_finish(); PopActiveSnapshot(); CommitTransactionCommand(); pgstat_report_activity(STATE_IDLE, NULL); } proc_exit(1); }
Datum palloc_bench(PG_FUNCTION_ARGS) { /* info for anyelement */ int i = 0; int32 ncontexts = PG_GETARG_INT32(0); int32 niterations = PG_GETARG_INT32(1); int32 allocsize = PG_GETARG_INT32(2); struct timeval start_time, end_time; /* memory contexts */ MemoryContext initctx = CurrentMemoryContext; MemoryContext ctx = initctx; /* switch to the per-group hash-table memory context */ for (i = 0; i < ncontexts; i++) { char name[256]; sprintf(name, "test context %d", i); #ifdef TRACKING_FLAG ctx = AllocSetContextCreateTracked(ctx, name, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, true); #else ctx = AllocSetContextCreate(ctx, name, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); #endif } MemoryContextSwitchTo(ctx); gettimeofday(&start_time, NULL); for (i = 0; i < niterations; i++) { char * p = palloc(allocsize); if (p != NULL) pfree(p); } gettimeofday(&end_time, NULL); MemoryContextSwitchTo(initctx); elog(WARNING, "duration = %.2f ms", (end_time.tv_sec - start_time.tv_sec) * 1000 + (end_time.tv_usec - start_time.tv_usec) / 1000.0); PG_RETURN_VOID(); }
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; /* 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. */ pgxml_parser_init(); /* For each row i.e. document returned from SPI */ for (i = 0; i < proc; i++) { char *pkey; char *xmldoc; xmlDocPtr doctree; 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) { xmlFreeDoc(doctree); xml_ereport(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); } xmlFreeDoc(doctree); if (pkey) pfree(pkey); if (xmldoc) pfree(xmldoc); } 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; }
static ArrayType * enum_range_internal(Oid enumtypoid, Oid lower, Oid upper) { ArrayType *result; Relation enum_rel; Relation enum_idx; SysScanDesc enum_scan; HeapTuple enum_tuple; ScanKeyData skey; Datum *elems; int max, cnt; bool left_found; /* * Scan the enum members in order using pg_enum_typid_sortorder_index. * Note we must not use the syscache, and must use an MVCC snapshot here. * See comments for RenumberEnumType in catalog/pg_enum.c for more info. */ ScanKeyInit(&skey, Anum_pg_enum_enumtypid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(enumtypoid)); enum_rel = heap_open(EnumRelationId, AccessShareLock); enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock); enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, GetTransactionSnapshot(), 1, &skey); max = 64; elems = (Datum *) palloc(max * sizeof(Datum)); cnt = 0; left_found = !OidIsValid(lower); while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection))) { Oid enum_oid = HeapTupleGetOid(enum_tuple); if (!left_found && lower == enum_oid) left_found = true; if (left_found) { if (cnt >= max) { max *= 2; elems = (Datum *) repalloc(elems, max * sizeof(Datum)); } elems[cnt++] = ObjectIdGetDatum(enum_oid); } if (OidIsValid(upper) && upper == enum_oid) break; } systable_endscan_ordered(enum_scan); index_close(enum_idx, AccessShareLock); heap_close(enum_rel, AccessShareLock); /* and build the result array */ /* note this hardwires some details about the representation of Oid */ result = construct_array(elems, cnt, enumtypoid, sizeof(Oid), true, 'i'); pfree(elems); return result; }
/* * compute_array_stats() -- compute statistics for an array column * * This function computes statistics useful for determining selectivity of * the array operators <@, &&, and @>. It is invoked by ANALYZE via the * compute_stats hook after sample rows have been collected. * * We also invoke the standard compute_stats function, which will compute * "scalar" statistics relevant to the btree-style array comparison operators. * However, exact duplicates of an entire array may be rare despite many * arrays sharing individual elements. This especially afflicts long arrays, * which are also liable to lack all scalar statistics due to the low * WIDTH_THRESHOLD used in analyze.c. So, in addition to the standard stats, * we find the most common array elements and compute a histogram of distinct * element counts. * * The algorithm used is Lossy Counting, as proposed in the paper "Approximate * frequency counts over data streams" by G. S. Manku and R. Motwani, in * Proceedings of the 28th International Conference on Very Large Data Bases, * Hong Kong, China, August 2002, section 4.2. The paper is available at * http://www.vldb.org/conf/2002/S10P03.pdf * * The Lossy Counting (aka LC) algorithm goes like this: * Let s be the threshold frequency for an item (the minimum frequency we * are interested in) and epsilon the error margin for the frequency. Let D * be a set of triples (e, f, delta), where e is an element value, f is that * element's frequency (actually, its current occurrence count) and delta is * the maximum error in f. We start with D empty and process the elements in * batches of size w. (The batch size is also known as "bucket size" and is * equal to 1/epsilon.) Let the current batch number be b_current, starting * with 1. For each element e we either increment its f count, if it's * already in D, or insert a new triple into D with values (e, 1, b_current * - 1). After processing each batch we prune D, by removing from it all * elements with f + delta <= b_current. After the algorithm finishes we * suppress all elements from D that do not satisfy f >= (s - epsilon) * N, * where N is the total number of elements in the input. We emit the * remaining elements with estimated frequency f/N. The LC paper proves * that this algorithm finds all elements with true frequency at least s, * and that no frequency is overestimated or is underestimated by more than * epsilon. Furthermore, given reasonable assumptions about the input * distribution, the required table size is no more than about 7 times w. * * In the absence of a principled basis for other particular values, we * follow ts_typanalyze() and use parameters s = 0.07/K, epsilon = s/10. * But we leave out the correction for stopwords, which do not apply to * arrays. These parameters give bucket width w = K/0.007 and maximum * expected hashtable size of about 1000 * K. * * Elements may repeat within an array. Since duplicates do not change the * behavior of <@, && or @>, we want to count each element only once per * array. Therefore, we store in the finished pg_statistic entry each * element's frequency as the fraction of all non-null rows that contain it. * We divide the raw counts by nonnull_cnt to get those figures. */ static void compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows) { ArrayAnalyzeExtraData *extra_data; int num_mcelem; int null_cnt = 0; int null_elem_cnt = 0; int analyzed_rows = 0; /* This is D from the LC algorithm. */ HTAB *elements_tab; HASHCTL elem_hash_ctl; HASH_SEQ_STATUS scan_status; /* This is the current bucket number from the LC algorithm */ int b_current; /* This is 'w' from the LC algorithm */ int bucket_width; int array_no; int64 element_no; TrackItem *item; int slot_idx; HTAB *count_tab; HASHCTL count_hash_ctl; DECountItem *count_item; extra_data = (ArrayAnalyzeExtraData *) stats->extra_data; /* * Invoke analyze.c's standard analysis function to create scalar-style * stats for the column. It will expect its own extra_data pointer, so * temporarily install that. */ stats->extra_data = extra_data->std_extra_data; extra_data->std_compute_stats(stats, fetchfunc, samplerows, totalrows); stats->extra_data = extra_data; /* * Set up static pointer for use by subroutines. We wait till here in * case std_compute_stats somehow recursively invokes us (probably not * possible, but ...) */ array_extra_data = extra_data; /* * We want statistics_target * 10 elements in the MCELEM array. This * multiplier is pretty arbitrary, but is meant to reflect the fact that * the number of individual elements tracked in pg_statistic ought to be * more than the number of values for a simple scalar column. */ num_mcelem = stats->attr->attstattarget * 10; /* * We set bucket width equal to num_mcelem / 0.007 as per the comment * above. */ bucket_width = num_mcelem * 1000 / 7; /* * Create the hashtable. It will be in local memory, so we don't need to * worry about overflowing the initial size. Also we don't need to pay any * attention to locking and memory management. */ MemSet(&elem_hash_ctl, 0, sizeof(elem_hash_ctl)); elem_hash_ctl.keysize = sizeof(Datum); elem_hash_ctl.entrysize = sizeof(TrackItem); elem_hash_ctl.hash = element_hash; elem_hash_ctl.match = element_match; elem_hash_ctl.hcxt = CurrentMemoryContext; elements_tab = hash_create("Analyzed elements table", num_mcelem, &elem_hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT); /* hashtable for array distinct elements counts */ MemSet(&count_hash_ctl, 0, sizeof(count_hash_ctl)); count_hash_ctl.keysize = sizeof(int); count_hash_ctl.entrysize = sizeof(DECountItem); count_hash_ctl.hcxt = CurrentMemoryContext; count_tab = hash_create("Array distinct element count table", 64, &count_hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); /* Initialize counters. */ b_current = 1; element_no = 0; /* Loop over the arrays. */ for (array_no = 0; array_no < samplerows; array_no++) { Datum value; bool isnull; ArrayType *array; int num_elems; Datum *elem_values; bool *elem_nulls; bool null_present; int j; int64 prev_element_no = element_no; int distinct_count; bool count_item_found; vacuum_delay_point(); value = fetchfunc(stats, array_no, &isnull); if (isnull) { /* array is null, just count that */ null_cnt++; continue; } /* Skip too-large values. */ if (toast_raw_datum_size(value) > ARRAY_WIDTH_THRESHOLD) continue; else analyzed_rows++; /* * Now detoast the array if needed, and deconstruct into datums. */ array = DatumGetArrayTypeP(value); Assert(ARR_ELEMTYPE(array) == extra_data->type_id); deconstruct_array(array, extra_data->type_id, extra_data->typlen, extra_data->typbyval, extra_data->typalign, &elem_values, &elem_nulls, &num_elems); /* * We loop through the elements in the array and add them to our * tracking hashtable. */ null_present = false; for (j = 0; j < num_elems; j++) { Datum elem_value; bool found; /* No null element processing other than flag setting here */ if (elem_nulls[j]) { null_present = true; continue; } /* Lookup current element in hashtable, adding it if new */ elem_value = elem_values[j]; item = (TrackItem *) hash_search(elements_tab, (const void *) &elem_value, HASH_ENTER, &found); if (found) { /* The element value is already on the tracking list */ /* * The operators we assist ignore duplicate array elements, so * count a given distinct element only once per array. */ if (item->last_container == array_no) continue; item->frequency++; item->last_container = array_no; } else { /* Initialize new tracking list element */ /* * If element type is pass-by-reference, we must copy it into * palloc'd space, so that we can release the array below. (We * do this so that the space needed for element values is * limited by the size of the hashtable; if we kept all the * array values around, it could be much more.) */ item->key = datumCopy(elem_value, extra_data->typbyval, extra_data->typlen); item->frequency = 1; item->delta = b_current - 1; item->last_container = array_no; } /* element_no is the number of elements processed (ie N) */ element_no++; /* We prune the D structure after processing each bucket */ if (element_no % bucket_width == 0) { prune_element_hashtable(elements_tab, b_current); b_current++; } } /* Count null element presence once per array. */ if (null_present) null_elem_cnt++; /* Update frequency of the particular array distinct element count. */ distinct_count = (int) (element_no - prev_element_no); count_item = (DECountItem *) hash_search(count_tab, &distinct_count, HASH_ENTER, &count_item_found); if (count_item_found) count_item->frequency++; else count_item->frequency = 1; /* Free memory allocated while detoasting. */ if (PointerGetDatum(array) != value) pfree(array); pfree(elem_values); pfree(elem_nulls); } /* Skip pg_statistic slots occupied by standard statistics */ slot_idx = 0; while (slot_idx < STATISTIC_NUM_SLOTS && stats->stakind[slot_idx] != 0) slot_idx++; if (slot_idx > STATISTIC_NUM_SLOTS - 2) elog(ERROR, "insufficient pg_statistic slots for array stats"); /* We can only compute real stats if we found some non-null values. */ if (analyzed_rows > 0) { int nonnull_cnt = analyzed_rows; int count_items_count; int i; TrackItem **sort_table; int track_len; int64 cutoff_freq; int64 minfreq, maxfreq; /* * We assume the standard stats code already took care of setting * stats_valid, stanullfrac, stawidth, stadistinct. We'd have to * re-compute those values if we wanted to not store the standard * stats. */ /* * Construct an array of the interesting hashtable items, that is, * those meeting the cutoff frequency (s - epsilon)*N. Also identify * the minimum and maximum frequencies among these items. * * Since epsilon = s/10 and bucket_width = 1/epsilon, the cutoff * frequency is 9*N / bucket_width. */ cutoff_freq = 9 * element_no / bucket_width; i = hash_get_num_entries(elements_tab); /* surely enough space */ sort_table = (TrackItem **) palloc(sizeof(TrackItem *) * i); hash_seq_init(&scan_status, elements_tab); track_len = 0; minfreq = element_no; maxfreq = 0; while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL) { if (item->frequency > cutoff_freq) { sort_table[track_len++] = item; minfreq = Min(minfreq, item->frequency); maxfreq = Max(maxfreq, item->frequency); } } Assert(track_len <= i); /* emit some statistics for debug purposes */ elog(DEBUG3, "compute_array_stats: target # mces = %d, " "bucket width = %d, " "# elements = " INT64_FORMAT ", hashtable size = %d, " "usable entries = %d", num_mcelem, bucket_width, element_no, i, track_len); /* * If we obtained more elements than we really want, get rid of those * with least frequencies. The easiest way is to qsort the array into * descending frequency order and truncate the array. */ if (num_mcelem < track_len) { qsort(sort_table, track_len, sizeof(TrackItem *), trackitem_compare_frequencies_desc); /* reset minfreq to the smallest frequency we're keeping */ minfreq = sort_table[num_mcelem - 1]->frequency; } else num_mcelem = track_len; /* Generate MCELEM slot entry */ if (num_mcelem > 0) { MemoryContext old_context; Datum *mcelem_values; float4 *mcelem_freqs; /* * We want to store statistics sorted on the element value using * the element type's default comparison function. This permits * fast binary searches in selectivity estimation functions. */ qsort(sort_table, num_mcelem, sizeof(TrackItem *), trackitem_compare_element); /* Must copy the target values into anl_context */ old_context = MemoryContextSwitchTo(stats->anl_context); /* * We sorted statistics on the element value, but we want to be * able to find the minimal and maximal frequencies without going * through all the values. We also want the frequency of null * elements. Store these three values at the end of mcelem_freqs. */ mcelem_values = (Datum *) palloc(num_mcelem * sizeof(Datum)); mcelem_freqs = (float4 *) palloc((num_mcelem + 3) * sizeof(float4)); /* * See comments above about use of nonnull_cnt as the divisor for * the final frequency estimates. */ for (i = 0; i < num_mcelem; i++) { TrackItem *item = sort_table[i]; mcelem_values[i] = datumCopy(item->key, extra_data->typbyval, extra_data->typlen); mcelem_freqs[i] = (double) item->frequency / (double) nonnull_cnt; } mcelem_freqs[i++] = (double) minfreq / (double) nonnull_cnt; mcelem_freqs[i++] = (double) maxfreq / (double) nonnull_cnt; mcelem_freqs[i++] = (double) null_elem_cnt / (double) nonnull_cnt; MemoryContextSwitchTo(old_context); stats->stakind[slot_idx] = STATISTIC_KIND_MCELEM; stats->staop[slot_idx] = extra_data->eq_opr; stats->stanumbers[slot_idx] = mcelem_freqs; /* See above comment about extra stanumber entries */ stats->numnumbers[slot_idx] = num_mcelem + 3; stats->stavalues[slot_idx] = mcelem_values; stats->numvalues[slot_idx] = num_mcelem; /* We are storing values of element type */ stats->statypid[slot_idx] = extra_data->type_id; stats->statyplen[slot_idx] = extra_data->typlen; stats->statypbyval[slot_idx] = extra_data->typbyval; stats->statypalign[slot_idx] = extra_data->typalign; slot_idx++; } /* Generate DECHIST slot entry */ count_items_count = hash_get_num_entries(count_tab); if (count_items_count > 0) { int num_hist = stats->attr->attstattarget; DECountItem **sorted_count_items; int j; int delta; int64 frac; float4 *hist; /* num_hist must be at least 2 for the loop below to work */ num_hist = Max(num_hist, 2); /* * Create an array of DECountItem pointers, and sort them into * increasing count order. */ sorted_count_items = (DECountItem **) palloc(sizeof(DECountItem *) * count_items_count); hash_seq_init(&scan_status, count_tab); j = 0; while ((count_item = (DECountItem *) hash_seq_search(&scan_status)) != NULL) { sorted_count_items[j++] = count_item; } qsort(sorted_count_items, count_items_count, sizeof(DECountItem *), countitem_compare_count); /* * Prepare to fill stanumbers with the histogram, followed by the * average count. This array must be stored in anl_context. */ hist = (float4 *) MemoryContextAlloc(stats->anl_context, sizeof(float4) * (num_hist + 1)); hist[num_hist] = (double) element_no / (double) nonnull_cnt; /*---------- * Construct the histogram of distinct-element counts (DECs). * * The object of this loop is to copy the min and max DECs to * hist[0] and hist[num_hist - 1], along with evenly-spaced DECs * in between (where "evenly-spaced" is with reference to the * whole input population of arrays). If we had a complete sorted * array of DECs, one per analyzed row, the i'th hist value would * come from DECs[i * (analyzed_rows - 1) / (num_hist - 1)] * (compare the histogram-making loop in compute_scalar_stats()). * But instead of that we have the sorted_count_items[] array, * which holds unique DEC values with their frequencies (that is, * a run-length-compressed version of the full array). So we * control advancing through sorted_count_items[] with the * variable "frac", which is defined as (x - y) * (num_hist - 1), * where x is the index in the notional DECs array corresponding * to the start of the next sorted_count_items[] element's run, * and y is the index in DECs from which we should take the next * histogram value. We have to advance whenever x <= y, that is * frac <= 0. The x component is the sum of the frequencies seen * so far (up through the current sorted_count_items[] element), * and of course y * (num_hist - 1) = i * (analyzed_rows - 1), * per the subscript calculation above. (The subscript calculation * implies dropping any fractional part of y; in this formulation * that's handled by not advancing until frac reaches 1.) * * Even though frac has a bounded range, it could overflow int32 * when working with very large statistics targets, so we do that * math in int64. *---------- */ delta = analyzed_rows - 1; j = 0; /* current index in sorted_count_items */ /* Initialize frac for sorted_count_items[0]; y is initially 0 */ frac = (int64) sorted_count_items[0]->frequency * (num_hist - 1); for (i = 0; i < num_hist; i++) { while (frac <= 0) { /* Advance, and update x component of frac */ j++; frac += (int64) sorted_count_items[j]->frequency * (num_hist - 1); } hist[i] = sorted_count_items[j]->count; frac -= delta; /* update y for upcoming i increment */ } Assert(j == count_items_count - 1); stats->stakind[slot_idx] = STATISTIC_KIND_DECHIST; stats->staop[slot_idx] = extra_data->eq_opr; stats->stanumbers[slot_idx] = hist; stats->numnumbers[slot_idx] = num_hist + 1; slot_idx++; } } /* * We don't need to bother cleaning up any of our temporary palloc's. The * hashtable should also go away, as it used a child memory context. */ }
/* * CreateConstraintEntry * Create a constraint table entry. * * Subsidiary records (such as triggers or indexes to implement the * constraint) are *not* created here. But we do make dependency links * from the constraint to the things it depends on. */ Oid CreateConstraintEntry(const char *constraintName, Oid constraintNamespace, char constraintType, bool isDeferrable, bool isDeferred, Oid relId, const int16 *constraintKey, int constraintNKeys, Oid domainId, Oid indexRelId, Oid foreignRelId, const int16 *foreignKey, const Oid *pfEqOp, const Oid *ppEqOp, const Oid *ffEqOp, int foreignNKeys, char foreignUpdateType, char foreignDeleteType, char foreignMatchType, const Oid *exclOp, Node *conExpr, const char *conBin, const char *conSrc, bool conIsLocal, int conInhCount) { Relation conDesc; Oid conOid; HeapTuple tup; bool nulls[Natts_pg_constraint]; Datum values[Natts_pg_constraint]; ArrayType *conkeyArray; ArrayType *confkeyArray; ArrayType *conpfeqopArray; ArrayType *conppeqopArray; ArrayType *conffeqopArray; ArrayType *conexclopArray; NameData cname; int i; ObjectAddress conobject; conDesc = heap_open(ConstraintRelationId, RowExclusiveLock); Assert(constraintName); namestrcpy(&cname, constraintName); /* * Convert C arrays into Postgres arrays. */ if (constraintNKeys > 0) { Datum *conkey; conkey = (Datum *) palloc(constraintNKeys * sizeof(Datum)); for (i = 0; i < constraintNKeys; i++) conkey[i] = Int16GetDatum(constraintKey[i]); conkeyArray = construct_array(conkey, constraintNKeys, INT2OID, 2, true, 's'); } else conkeyArray = NULL; if (foreignNKeys > 0) { Datum *fkdatums; fkdatums = (Datum *) palloc(foreignNKeys * sizeof(Datum)); for (i = 0; i < foreignNKeys; i++) fkdatums[i] = Int16GetDatum(foreignKey[i]); confkeyArray = construct_array(fkdatums, foreignNKeys, INT2OID, 2, true, 's'); for (i = 0; i < foreignNKeys; i++) fkdatums[i] = ObjectIdGetDatum(pfEqOp[i]); conpfeqopArray = construct_array(fkdatums, foreignNKeys, OIDOID, sizeof(Oid), true, 'i'); for (i = 0; i < foreignNKeys; i++) fkdatums[i] = ObjectIdGetDatum(ppEqOp[i]); conppeqopArray = construct_array(fkdatums, foreignNKeys, OIDOID, sizeof(Oid), true, 'i'); for (i = 0; i < foreignNKeys; i++) fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]); conffeqopArray = construct_array(fkdatums, foreignNKeys, OIDOID, sizeof(Oid), true, 'i'); } else { confkeyArray = NULL; conpfeqopArray = NULL; conppeqopArray = NULL; conffeqopArray = NULL; } if (exclOp != NULL) { Datum *opdatums; opdatums = (Datum *) palloc(constraintNKeys * sizeof(Datum)); for (i = 0; i < constraintNKeys; i++) opdatums[i] = ObjectIdGetDatum(exclOp[i]); conexclopArray = construct_array(opdatums, constraintNKeys, OIDOID, sizeof(Oid), true, 'i'); } else conexclopArray = NULL; /* initialize nulls and values */ for (i = 0; i < Natts_pg_constraint; i++) { nulls[i] = false; values[i] = (Datum) NULL; } values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname); values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace); values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType); values[Anum_pg_constraint_condeferrable - 1] = BoolGetDatum(isDeferrable); values[Anum_pg_constraint_condeferred - 1] = BoolGetDatum(isDeferred); values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId); values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId); values[Anum_pg_constraint_conindid - 1] = ObjectIdGetDatum(indexRelId); values[Anum_pg_constraint_confrelid - 1] = ObjectIdGetDatum(foreignRelId); values[Anum_pg_constraint_confupdtype - 1] = CharGetDatum(foreignUpdateType); values[Anum_pg_constraint_confdeltype - 1] = CharGetDatum(foreignDeleteType); values[Anum_pg_constraint_confmatchtype - 1] = CharGetDatum(foreignMatchType); values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal); values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount); if (conkeyArray) values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray); else nulls[Anum_pg_constraint_conkey - 1] = true; if (confkeyArray) values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(confkeyArray); else nulls[Anum_pg_constraint_confkey - 1] = true; if (conpfeqopArray) values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray); else nulls[Anum_pg_constraint_conpfeqop - 1] = true; if (conppeqopArray) values[Anum_pg_constraint_conppeqop - 1] = PointerGetDatum(conppeqopArray); else nulls[Anum_pg_constraint_conppeqop - 1] = true; if (conffeqopArray) values[Anum_pg_constraint_conffeqop - 1] = PointerGetDatum(conffeqopArray); else nulls[Anum_pg_constraint_conffeqop - 1] = true; if (conexclopArray) values[Anum_pg_constraint_conexclop - 1] = PointerGetDatum(conexclopArray); else nulls[Anum_pg_constraint_conexclop - 1] = true; /* * initialize the binary form of the check constraint. */ if (conBin) values[Anum_pg_constraint_conbin - 1] = CStringGetTextDatum(conBin); else nulls[Anum_pg_constraint_conbin - 1] = true; /* * initialize the text form of the check constraint */ if (conSrc) values[Anum_pg_constraint_consrc - 1] = CStringGetTextDatum(conSrc); else nulls[Anum_pg_constraint_consrc - 1] = true; tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls); conOid = simple_heap_insert(conDesc, tup); /* update catalog indexes */ CatalogUpdateIndexes(conDesc, tup); conobject.classId = ConstraintRelationId; conobject.objectId = conOid; conobject.objectSubId = 0; heap_close(conDesc, RowExclusiveLock); if (OidIsValid(relId)) { /* * Register auto dependency from constraint to owning relation, or to * specific column(s) if any are mentioned. */ ObjectAddress relobject; relobject.classId = RelationRelationId; relobject.objectId = relId; if (constraintNKeys > 0) { for (i = 0; i < constraintNKeys; i++) { relobject.objectSubId = constraintKey[i]; recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO); } } else { relobject.objectSubId = 0; recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO); } } if (OidIsValid(domainId)) { /* * Register auto dependency from constraint to owning domain */ ObjectAddress domobject; domobject.classId = TypeRelationId; domobject.objectId = domainId; domobject.objectSubId = 0; recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO); } if (OidIsValid(foreignRelId)) { /* * Register normal dependency from constraint to foreign relation, or * to specific column(s) if any are mentioned. */ ObjectAddress relobject; relobject.classId = RelationRelationId; relobject.objectId = foreignRelId; if (foreignNKeys > 0) { for (i = 0; i < foreignNKeys; i++) { relobject.objectSubId = foreignKey[i]; recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL); } } else { relobject.objectSubId = 0; recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL); } } if (OidIsValid(indexRelId) && constraintType == CONSTRAINT_FOREIGN) { /* * Register normal dependency on the unique index that supports a * foreign-key constraint. (Note: for indexes associated with unique * or primary-key constraints, the dependency runs the other way, and * is not made here.) */ ObjectAddress relobject; relobject.classId = RelationRelationId; relobject.objectId = indexRelId; relobject.objectSubId = 0; recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL); } if (foreignNKeys > 0) { /* * Register normal dependencies on the equality operators that support * a foreign-key constraint. If the PK and FK types are the same then * all three operators for a column are the same; otherwise they are * different. */ ObjectAddress oprobject; oprobject.classId = OperatorRelationId; oprobject.objectSubId = 0; for (i = 0; i < foreignNKeys; i++) { oprobject.objectId = pfEqOp[i]; recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL); if (ppEqOp[i] != pfEqOp[i]) { oprobject.objectId = ppEqOp[i]; recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL); } if (ffEqOp[i] != pfEqOp[i]) { oprobject.objectId = ffEqOp[i]; recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL); } } } /* * We don't bother to register dependencies on the exclusion operators of * an exclusion constraint. We assume they are members of the opclass * supporting the index, so there's an indirect dependency via that. (This * would be pretty dicey for cross-type operators, but exclusion operators * can never be cross-type.) */ if (conExpr != NULL) { /* * Register dependencies from constraint to objects mentioned in CHECK * expression. */ recordDependencyOnSingleRelExpr(&conobject, conExpr, relId, DEPENDENCY_NORMAL, DEPENDENCY_NORMAL); } return conOid; }
/* * nodeRead - * Slightly higher-level reader. * * This routine applies some semantic knowledge on top of the purely * lexical tokenizer pg_strtok(). It can read * * Value token nodes (integers, floats, or strings); * * General nodes (via parseNodeString() from readfuncs.c); * * Lists of the above; * * Lists of integers or OIDs. * The return value is declared void *, not Node *, to avoid having to * cast it explicitly in callers that assign to fields of different types. * * External callers should always pass NULL/0 for the arguments. Internally * a non-NULL token may be passed when the upper recursion level has already * scanned the first token of a node's representation. * * We assume pg_strtok is already initialized with a string to read (hence * this should only be invoked from within a stringToNode operation). */ void * nodeRead(char *token, int tok_len) { Node *result; NodeTag type; if (token == NULL) /* need to read a token? */ { token = pg_strtok(&tok_len); if (token == NULL) /* end of input */ return NULL; } type = nodeTokenType(token, tok_len); switch ((int) type) { case LEFT_BRACE: result = parseNodeString(); token = pg_strtok(&tok_len); if (token == NULL || token[0] != '}') elog(ERROR, "did not find '}' at end of input node"); break; case LEFT_PAREN: { List *l = NIL; /*---------- * Could be an integer list: (i int int ...) * or an OID list: (o int int ...) * or a list of nodes/values: (node node ...) *---------- */ token = pg_strtok(&tok_len); if (token == NULL) elog(ERROR, "unterminated List structure"); if (tok_len == 1 && token[0] == 'i') { /* List of integers */ for (;;) { int val; char *endptr; token = pg_strtok(&tok_len); if (token == NULL) elog(ERROR, "unterminated List structure"); if (token[0] == ')') break; val = (int) strtol(token, &endptr, 10); if (endptr != token + tok_len) elog(ERROR, "unrecognized integer: \"%.*s\"", tok_len, token); l = lappend_int(l, val); } } else if (tok_len == 1 && token[0] == 'o') { /* List of OIDs */ for (;;) { Oid val; char *endptr; token = pg_strtok(&tok_len); if (token == NULL) elog(ERROR, "unterminated List structure"); if (token[0] == ')') break; val = (Oid) strtoul(token, &endptr, 10); if (endptr != token + tok_len) elog(ERROR, "unrecognized OID: \"%.*s\"", tok_len, token); l = lappend_oid(l, val); } } else { /* List of other node types */ for (;;) { /* We have already scanned next token... */ if (token[0] == ')') break; l = lappend(l, nodeRead(token, tok_len)); token = pg_strtok(&tok_len); if (token == NULL) elog(ERROR, "unterminated List structure"); } } result = (Node *) l; break; } case RIGHT_PAREN: elog(ERROR, "unexpected right parenthesis"); result = NULL; /* keep compiler happy */ break; case OTHER_TOKEN: if (tok_len == 0) { /* must be "<>" --- represents a null pointer */ result = NULL; } else { elog(ERROR, "unrecognized token: \"%.*s\"", tok_len, token); result = NULL; /* keep compiler happy */ } break; case T_Integer: /* * we know that the token terminates on a char atol will stop at */ result = (Node *) makeInteger(atol(token)); break; case T_Float: { char *fval = (char *) palloc(tok_len + 1); memcpy(fval, token, tok_len); fval[tok_len] = '\0'; result = (Node *) makeFloat(fval); } break; case T_String: /* need to remove leading and trailing quotes, and backslashes */ result = (Node *) makeString(debackslash(token + 1, tok_len - 2)); break; case T_BitString: { char *val = palloc(tok_len); /* skip leading 'b' */ memcpy(val, token + 1, tok_len - 1); val[tok_len - 1] = '\0'; result = (Node *) makeBitString(val); break; } default: elog(ERROR, "unrecognized node type: %d", (int) type); result = NULL; /* keep compiler happy */ break; } return (void *) result; }
/* * lowerstr_with_len --- fold string to lower case * * Input string need not be null-terminated. * * Returned string is palloc'd */ char * lowerstr_with_len(const char *str, int len) { char *out; if (len == 0) return pstrdup(""); #ifdef USE_WIDE_UPPER_LOWER /* * Use wide char code only when max encoding length > 1 and ctype != C. * Some operating systems fail with multi-byte encodings and a C locale. * Also, for a C locale there is no need to process as multibyte. From * backend/utils/adt/oracle_compat.c Teodor */ if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c()) { wchar_t *wstr, *wptr; int wlen; /* * alloc number of wchar_t for worst case, len contains number of * bytes >= number of characters and alloc 1 wchar_t for 0, because * wchar2char wants zero-terminated string */ wptr = wstr = (wchar_t *) palloc(sizeof(wchar_t) * (len + 1)); wlen = char2wchar(wstr, len + 1, str, len); Assert(wlen <= len); while (*wptr) { *wptr = towlower((wint_t) *wptr); wptr++; } /* * Alloc result string for worst case + '\0' */ len = pg_database_encoding_max_length() * wlen + 1; out = (char *) palloc(len); wlen = wchar2char(out, wstr, len); pfree(wstr); if (wlen < 0) ereport(ERROR, (errcode(ERRCODE_CHARACTER_NOT_IN_REPERTOIRE), errmsg("conversion from wchar_t to server encoding failed: %m"))); Assert(wlen < len); } else #endif /* USE_WIDE_UPPER_LOWER */ { const char *ptr = str; char *outptr; outptr = out = (char *) palloc(sizeof(char) * (len + 1)); while ((ptr - str) < len && *ptr) { *outptr++ = tolower(TOUCHAR(ptr)); ptr++; } *outptr = '\0'; } return out; }
/* * create and populate the crosstab tuplestore using the provided source query */ static Tuplestorestate * get_crosstab_tuplestore(char *sql, HTAB *crosstab_hash, TupleDesc tupdesc, MemoryContext per_query_ctx, bool randomAccess) { Tuplestorestate *tupstore; int num_categories = hash_get_num_entries(crosstab_hash); AttInMetadata *attinmeta = TupleDescGetAttInMetadata(tupdesc); char **values; HeapTuple tuple; int ret; int proc; /* initialize our tuplestore (while still in query context!) */ tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) /* internal error */ elog(ERROR, "get_crosstab_tuplestore: SPI_connect returned %d", ret); /* Now retrieve the crosstab source rows */ ret = SPI_execute(sql, true, 0); proc = SPI_processed; /* Check for qualifying tuples */ if ((ret == SPI_OK_SELECT) && (proc > 0)) { SPITupleTable *spi_tuptable = SPI_tuptable; TupleDesc spi_tupdesc = spi_tuptable->tupdesc; int ncols = spi_tupdesc->natts; char *rowid; char *lastrowid = NULL; bool firstpass = true; int i, j; int result_ncols; if (num_categories == 0) { /* no qualifying category tuples */ ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("provided \"categories\" SQL must " \ "return 1 column of at least one row"))); } /* * The provided SQL query must always return at least three columns: * * 1. rowname the label for each row - column 1 in the final result * 2. category the label for each value-column in the final result 3. * value the values used to populate the value-columns * * If there are more than three columns, the last two are taken as * "category" and "values". The first column is taken as "rowname". * Additional columns (2 thru N-2) are assumed the same for the same * "rowname", and are copied into the result tuple from the first time * we encounter a particular rowname. */ if (ncols < 3) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid source data SQL statement"), errdetail("The provided SQL must return 3 " \ " columns; rowid, category, and values."))); result_ncols = (ncols - 2) + num_categories; /* Recheck to make sure we tuple descriptor still looks reasonable */ if (tupdesc->natts != result_ncols) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid return type"), errdetail("Query-specified return " \ "tuple has %d columns but crosstab " \ "returns %d.", tupdesc->natts, result_ncols))); /* allocate space */ values = (char **) palloc(result_ncols * sizeof(char *)); /* and make sure it's clear */ memset(values, '\0', result_ncols * sizeof(char *)); for (i = 0; i < proc; i++) { HeapTuple spi_tuple; crosstab_cat_desc *catdesc; char *catname; /* get the next sql result tuple */ spi_tuple = spi_tuptable->vals[i]; /* get the rowid from the current sql result tuple */ rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1); /* * if we're on a new output row, grab the column values up to * column N-2 now */ if (firstpass || !xstreq(lastrowid, rowid)) { /* * a new row means we need to flush the old one first, unless * we're on the very first row */ if (!firstpass) { /* rowid changed, flush the previous output row */ tuple = BuildTupleFromCStrings(attinmeta, values); tuplestore_puttuple(tupstore, tuple); for (j = 0; j < result_ncols; j++) xpfree(values[j]); } values[0] = rowid; for (j = 1; j < ncols - 2; j++) values[j] = SPI_getvalue(spi_tuple, spi_tupdesc, j + 1); /* we're no longer on the first pass */ firstpass = false; } /* look up the category and fill in the appropriate column */ catname = SPI_getvalue(spi_tuple, spi_tupdesc, ncols - 1); if (catname != NULL) { crosstab_HashTableLookup(crosstab_hash, catname, catdesc); if (catdesc) values[catdesc->attidx + ncols - 2] = SPI_getvalue(spi_tuple, spi_tupdesc, ncols); } xpfree(lastrowid); xpstrdup(lastrowid, rowid); } /* flush the last output row */ tuple = BuildTupleFromCStrings(attinmeta, values); tuplestore_puttuple(tupstore, tuple); } if (SPI_finish() != SPI_OK_FINISH) /* internal error */ elog(ERROR, "get_crosstab_tuplestore: SPI_finish() failed"); tuplestore_donestoring(tupstore); return tupstore; }
Datum gtsvector_compress(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); GISTENTRY *retval = entry; if (entry->leafkey) { /* tsvector */ SignTSVector *res; TSVector val = DatumGetTSVector(entry->key); int32 len; int32 *arr; WordEntry *ptr = ARRPTR(val); char *words = STRPTR(val); len = CALCGTSIZE(ARRKEY, val->size); res = (SignTSVector *) palloc(len); SET_VARSIZE(res, len); res->flag = ARRKEY; arr = GETARR(res); len = val->size; while (len--) { pg_crc32 c; INIT_LEGACY_CRC32(c); COMP_LEGACY_CRC32(c, words + ptr->pos, ptr->len); FIN_LEGACY_CRC32(c); *arr = *(int32 *) &c; arr++; ptr++; } len = uniqueint(GETARR(res), val->size); if (len != val->size) { /* * there is a collision of hash-function; len is always less than * val->size */ len = CALCGTSIZE(ARRKEY, len); res = (SignTSVector *) repalloc((void *) res, len); SET_VARSIZE(res, len); } /* make signature, if array is too long */ if (VARSIZE(res) > TOAST_INDEX_TARGET) { SignTSVector *ressign; len = CALCGTSIZE(SIGNKEY, 0); ressign = (SignTSVector *) palloc(len); SET_VARSIZE(ressign, len); ressign->flag = SIGNKEY; makesign(GETSIGN(ressign), res); res = ressign; } retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); gistentryinit(*retval, PointerGetDatum(res), entry->rel, entry->page, entry->offset, FALSE); } else if (ISSIGNKEY(DatumGetPointer(entry->key)) && !ISALLTRUE(DatumGetPointer(entry->key))) { int32 i, len; SignTSVector *res; BITVECP sign = GETSIGN(DatumGetPointer(entry->key)); LOOPBYTE { if ((sign[i] & 0xff) != 0xff) PG_RETURN_POINTER(retval); } len = CALCGTSIZE(SIGNKEY | ALLISTRUE, 0); res = (SignTSVector *) palloc(len); SET_VARSIZE(res, len); res->flag = SIGNKEY | ALLISTRUE; retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); gistentryinit(*retval, PointerGetDatum(res), entry->rel, entry->page, entry->offset, FALSE); }
/* * load up the categories hash table */ static HTAB * load_categories_hash(char *cats_sql, MemoryContext per_query_ctx) { HTAB *crosstab_hash; HASHCTL ctl; int ret; int proc; MemoryContext SPIcontext; /* initialize the category hash table */ MemSet(&ctl, 0, sizeof(ctl)); ctl.keysize = MAX_CATNAME_LEN; ctl.entrysize = sizeof(crosstab_HashEnt); ctl.hcxt = per_query_ctx; /* * use INIT_CATS, defined above as a guess of how many hash table entries * to create, initially */ crosstab_hash = hash_create("crosstab hash", INIT_CATS, &ctl, HASH_ELEM | HASH_CONTEXT); /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) /* internal error */ elog(ERROR, "load_categories_hash: SPI_connect returned %d", ret); /* Retrieve the category name rows */ ret = SPI_execute(cats_sql, true, 0); proc = SPI_processed; /* Check for qualifying tuples */ if ((ret == SPI_OK_SELECT) && (proc > 0)) { SPITupleTable *spi_tuptable = SPI_tuptable; TupleDesc spi_tupdesc = spi_tuptable->tupdesc; int i; /* * The provided categories SQL query must always return one column: * category - the label or identifier for each column */ if (spi_tupdesc->natts != 1) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("provided \"categories\" SQL must " \ "return 1 column of at least one row"))); for (i = 0; i < proc; i++) { crosstab_cat_desc *catdesc; char *catname; HeapTuple spi_tuple; /* get the next sql result tuple */ spi_tuple = spi_tuptable->vals[i]; /* get the category from the current sql result tuple */ catname = SPI_getvalue(spi_tuple, spi_tupdesc, 1); SPIcontext = MemoryContextSwitchTo(per_query_ctx); catdesc = (crosstab_cat_desc *) palloc(sizeof(crosstab_cat_desc)); catdesc->catname = catname; catdesc->attidx = i; /* Add the proc description block to the hashtable */ crosstab_HashTableInsert(crosstab_hash, catdesc); MemoryContextSwitchTo(SPIcontext); } } if (SPI_finish() != SPI_OK_FINISH) /* internal error */ elog(ERROR, "load_categories_hash: SPI_finish() failed"); return crosstab_hash; }
Datum normal_rand(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; int call_cntr; int max_calls; normal_rand_fctx *fctx; float8 mean; float8 stddev; float8 carry_val; bool use_carry; MemoryContext oldcontext; /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) { /* create a function context for cross-call persistence */ funcctx = SRF_FIRSTCALL_INIT(); /* * switch to memory context appropriate for multiple function calls */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* total number of tuples to be returned */ funcctx->max_calls = PG_GETARG_UINT32(0); /* allocate memory for user context */ fctx = (normal_rand_fctx *) palloc(sizeof(normal_rand_fctx)); /* * Use fctx to keep track of upper and lower bounds from call to call. * It will also be used to carry over the spare value we get from the * Box-Muller algorithm so that we only actually calculate a new value * every other call. */ fctx->mean = PG_GETARG_FLOAT8(1); fctx->stddev = PG_GETARG_FLOAT8(2); fctx->carry_val = 0; fctx->use_carry = false; funcctx->user_fctx = fctx; MemoryContextSwitchTo(oldcontext); } /* stuff done on every call of the function */ funcctx = SRF_PERCALL_SETUP(); call_cntr = funcctx->call_cntr; max_calls = funcctx->max_calls; fctx = funcctx->user_fctx; mean = fctx->mean; stddev = fctx->stddev; carry_val = fctx->carry_val; use_carry = fctx->use_carry; if (call_cntr < max_calls) /* do when there is more left to send */ { float8 result; if (use_carry) { /* * reset use_carry and use second value obtained on last pass */ fctx->use_carry = false; result = carry_val; } else { float8 normval_1; float8 normval_2; /* Get the next two normal values */ get_normal_pair(&normval_1, &normval_2); /* use the first */ result = mean + (stddev * normval_1); /* and save the second */ fctx->carry_val = mean + (stddev * normval_2); fctx->use_carry = true; } /* send the result */ SRF_RETURN_NEXT(funcctx, Float8GetDatum(result)); } else /* do when there is no more left */ SRF_RETURN_DONE(funcctx); }
static Tuplestorestate * build_tuplestore_recursively(char *key_fld, char *parent_key_fld, char *relname, char *orderby_fld, char *branch_delim, char *start_with, char *branch, int level, int *serial, int max_depth, bool show_branch, bool show_serial, MemoryContext per_query_ctx, AttInMetadata *attinmeta, Tuplestorestate *tupstore) { TupleDesc tupdesc = attinmeta->tupdesc; int ret; int proc; int serial_column; StringInfoData sql; char **values; char *current_key; char *current_key_parent; char current_level[INT32_STRLEN]; char serial_str[INT32_STRLEN]; char *current_branch; HeapTuple tuple; if (max_depth > 0 && level > max_depth) return tupstore; initStringInfo(&sql); /* Build initial sql statement */ if (!show_serial) { appendStringInfo(&sql, "SELECT %s, %s FROM %s WHERE %s = %s AND %s IS NOT NULL AND %s <> %s", key_fld, parent_key_fld, relname, parent_key_fld, quote_literal_cstr(start_with), key_fld, key_fld, parent_key_fld); serial_column = 0; } else { appendStringInfo(&sql, "SELECT %s, %s FROM %s WHERE %s = %s AND %s IS NOT NULL AND %s <> %s ORDER BY %s", key_fld, parent_key_fld, relname, parent_key_fld, quote_literal_cstr(start_with), key_fld, key_fld, parent_key_fld, orderby_fld); serial_column = 1; } if (show_branch) values = (char **) palloc((CONNECTBY_NCOLS + serial_column) * sizeof(char *)); else values = (char **) palloc((CONNECTBY_NCOLS_NOBRANCH + serial_column) * sizeof(char *)); /* First time through, do a little setup */ if (level == 0) { /* root value is the one we initially start with */ values[0] = start_with; /* root value has no parent */ values[1] = NULL; /* root level is 0 */ sprintf(current_level, "%d", level); values[2] = current_level; /* root branch is just starting root value */ if (show_branch) values[3] = start_with; /* root starts the serial with 1 */ if (show_serial) { sprintf(serial_str, "%d", (*serial)++); if (show_branch) values[4] = serial_str; else values[3] = serial_str; } /* construct the tuple */ tuple = BuildTupleFromCStrings(attinmeta, values); /* now store it */ tuplestore_puttuple(tupstore, tuple); /* increment level */ level++; } /* Retrieve the desired rows */ ret = SPI_execute(sql.data, true, 0); proc = SPI_processed; /* Check for qualifying tuples */ if ((ret == SPI_OK_SELECT) && (proc > 0)) { HeapTuple spi_tuple; SPITupleTable *tuptable = SPI_tuptable; TupleDesc spi_tupdesc = tuptable->tupdesc; int i; StringInfoData branchstr; StringInfoData chk_branchstr; StringInfoData chk_current_key; /* First time through, do a little more setup */ if (level == 0) { /* * Check that return tupdesc is compatible with the one we got * from the query, but only at level 0 -- no need to check more * than once */ if (!compatConnectbyTupleDescs(tupdesc, spi_tupdesc)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid return type"), errdetail("Return and SQL tuple descriptions are " \ "incompatible."))); } initStringInfo(&branchstr); initStringInfo(&chk_branchstr); initStringInfo(&chk_current_key); for (i = 0; i < proc; i++) { /* initialize branch for this pass */ appendStringInfo(&branchstr, "%s", branch); appendStringInfo(&chk_branchstr, "%s%s%s", branch_delim, branch, branch_delim); /* get the next sql result tuple */ spi_tuple = tuptable->vals[i]; /* get the current key and parent */ current_key = SPI_getvalue(spi_tuple, spi_tupdesc, 1); appendStringInfo(&chk_current_key, "%s%s%s", branch_delim, current_key, branch_delim); current_key_parent = pstrdup(SPI_getvalue(spi_tuple, spi_tupdesc, 2)); /* get the current level */ sprintf(current_level, "%d", level); /* check to see if this key is also an ancestor */ if (strstr(chk_branchstr.data, chk_current_key.data)) elog(ERROR, "infinite recursion detected"); /* OK, extend the branch */ appendStringInfo(&branchstr, "%s%s", branch_delim, current_key); current_branch = branchstr.data; /* build a tuple */ values[0] = pstrdup(current_key); values[1] = current_key_parent; values[2] = current_level; if (show_branch) values[3] = current_branch; if (show_serial) { sprintf(serial_str, "%d", (*serial)++); if (show_branch) values[4] = serial_str; else values[3] = serial_str; } tuple = BuildTupleFromCStrings(attinmeta, values); xpfree(current_key); xpfree(current_key_parent); /* store the tuple for later use */ tuplestore_puttuple(tupstore, tuple); heap_freetuple(tuple); /* recurse using current_key_parent as the new start_with */ tupstore = build_tuplestore_recursively(key_fld, parent_key_fld, relname, orderby_fld, branch_delim, values[0], current_branch, level + 1, serial, max_depth, show_branch, show_serial, per_query_ctx, attinmeta, tupstore); /* reset branch for next pass */ resetStringInfo(&branchstr); resetStringInfo(&chk_branchstr); resetStringInfo(&chk_current_key); } xpfree(branchstr.data); xpfree(chk_branchstr.data); xpfree(chk_current_key.data); } return tupstore; }
/* ** The GiST PickSplit method for _intments ** We use Guttman's poly time split algorithm */ Datum g_int_picksplit(PG_FUNCTION_ARGS) { bytea *entryvec = (bytea *) PG_GETARG_POINTER(0); GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); OffsetNumber i, j; ArrayType *datum_alpha, *datum_beta; ArrayType *datum_l, *datum_r; ArrayType *union_d, *union_dl, *union_dr; ArrayType *inter_d; bool firsttime; float size_alpha, size_beta, size_union, size_inter; float size_waste, waste; float size_l, size_r; int nbytes; OffsetNumber seed_1 = 0, seed_2 = 0; OffsetNumber *left, *right; OffsetNumber maxoff; SPLITCOST *costvector; #ifdef GIST_DEBUG elog(DEBUG3, "--------picksplit %d", (VARSIZE(entryvec) - VARHDRSZ) / sizeof(GISTENTRY)); #endif maxoff = ((VARSIZE(entryvec) - VARHDRSZ) / sizeof(GISTENTRY)) - 2; nbytes = (maxoff + 2) * sizeof(OffsetNumber); v->spl_left = (OffsetNumber *) palloc(nbytes); v->spl_right = (OffsetNumber *) palloc(nbytes); firsttime = true; waste = 0.0; for (i = FirstOffsetNumber; i < maxoff; i = OffsetNumberNext(i)) { datum_alpha = (ArrayType *) DatumGetPointer(((GISTENTRY *) VARDATA(entryvec))[i].key); for (j = OffsetNumberNext(i); j <= maxoff; j = OffsetNumberNext(j)) { datum_beta = (ArrayType *) DatumGetPointer(((GISTENTRY *) VARDATA(entryvec))[j].key); /* compute the wasted space by unioning these guys */ /* size_waste = size_union - size_inter; */ union_d = inner_int_union(datum_alpha, datum_beta); rt__int_size(union_d, &size_union); inter_d = inner_int_inter(datum_alpha, datum_beta); rt__int_size(inter_d, &size_inter); size_waste = size_union - size_inter; pfree(union_d); if (inter_d != (ArrayType *) NULL) pfree(inter_d); /* * are these a more promising split that what we've already * seen? */ if (size_waste > waste || firsttime) { waste = size_waste; seed_1 = i; seed_2 = j; firsttime = false; } } } left = v->spl_left; v->spl_nleft = 0; right = v->spl_right; v->spl_nright = 0; if (seed_1 == 0 || seed_2 == 0) { seed_1 = 1; seed_2 = 2; } datum_alpha = (ArrayType *) DatumGetPointer(((GISTENTRY *) VARDATA(entryvec))[seed_1].key); datum_l = copy_intArrayType(datum_alpha); rt__int_size(datum_l, &size_l); datum_beta = (ArrayType *) DatumGetPointer(((GISTENTRY *) VARDATA(entryvec))[seed_2].key); datum_r = copy_intArrayType(datum_beta); rt__int_size(datum_r, &size_r); maxoff = OffsetNumberNext(maxoff); /* * sort entries */ costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff); for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) { costvector[i - 1].pos = i; datum_alpha = (ArrayType *) DatumGetPointer(((GISTENTRY *) VARDATA(entryvec))[i].key); union_d = inner_int_union(datum_l, datum_alpha); rt__int_size(union_d, &size_alpha); pfree(union_d); union_d = inner_int_union(datum_r, datum_alpha); rt__int_size(union_d, &size_beta); pfree(union_d); costvector[i - 1].cost = abs((size_alpha - size_l) - (size_beta - size_r)); } qsort((void *) costvector, maxoff, sizeof(SPLITCOST), comparecost); /* * Now split up the regions between the two seeds. An important * property of this split algorithm is that the split vector v has the * indices of items to be split in order in its left and right * vectors. We exploit this property by doing a merge in the code * that actually splits the page. * * For efficiency, we also place the new index tuple in this loop. This * is handled at the very end, when we have placed all the existing * tuples and i == maxoff + 1. */ for (j = 0; j < maxoff; j++) { i = costvector[j].pos; /* * If we've already decided where to place this item, just put it * on the right list. Otherwise, we need to figure out which page * needs the least enlargement in order to store the item. */ if (i == seed_1) { *left++ = i; v->spl_nleft++; continue; } else if (i == seed_2) { *right++ = i; v->spl_nright++; continue; } /* okay, which page needs least enlargement? */ datum_alpha = (ArrayType *) DatumGetPointer(((GISTENTRY *) VARDATA(entryvec))[i].key); union_dl = inner_int_union(datum_l, datum_alpha); union_dr = inner_int_union(datum_r, datum_alpha); rt__int_size(union_dl, &size_alpha); rt__int_size(union_dr, &size_beta); /* pick which page to add it to */ if (size_alpha - size_l < size_beta - size_r + WISH_F(v->spl_nleft, v->spl_nright, 0.01)) { if (datum_l) pfree(datum_l); if (union_dr) pfree(union_dr); datum_l = union_dl; size_l = size_alpha; *left++ = i; v->spl_nleft++; } else { if (datum_r) pfree(datum_r); if (union_dl) pfree(union_dl); datum_r = union_dr; size_r = size_beta; *right++ = i; v->spl_nright++; } } pfree(costvector); *right = *left = FirstOffsetNumber; datum_l->flags &= ~LEAFKEY; datum_r->flags &= ~LEAFKEY; v->spl_ldatum = PointerGetDatum(datum_l); v->spl_rdatum = PointerGetDatum(datum_r); PG_RETURN_POINTER(v); }
/* * Check whether supplied input is valid JSON. */ static void json_validate_cstring(char *input) { JsonLexContext lex; JsonParseStack *stack, *stacktop; int stacksize; /* Set up lexing context. */ lex.input = input; lex.token_terminator = lex.input; lex.line_number = 1; lex.line_start = input; /* Set up parse stack. */ stacksize = 32; stacktop = palloc(sizeof(JsonParseStack) * stacksize); stack = stacktop; stack->state = JSON_PARSE_VALUE; /* Main parsing loop. */ for (;;) { JsonStackOp op; /* Fetch next token. */ json_lex(&lex); /* Check for unexpected end of input. */ if (lex.token_start == NULL) report_parse_error(stack, &lex); redo: /* Figure out what to do with this token. */ op = JSON_STACKOP_NONE; switch (stack->state) { case JSON_PARSE_VALUE: if (lex.token_type != JSON_VALUE_INVALID) op = JSON_STACKOP_POP; else if (lex.token_start[0] == '[') stack->state = JSON_PARSE_ARRAY_START; else if (lex.token_start[0] == '{') stack->state = JSON_PARSE_OBJECT_START; else report_parse_error(stack, &lex); break; case JSON_PARSE_ARRAY_START: if (lex.token_type != JSON_VALUE_INVALID) stack->state = JSON_PARSE_ARRAY_NEXT; else if (lex.token_start[0] == ']') op = JSON_STACKOP_POP; else if (lex.token_start[0] == '[' || lex.token_start[0] == '{') { stack->state = JSON_PARSE_ARRAY_NEXT; op = JSON_STACKOP_PUSH_WITH_PUSHBACK; } else report_parse_error(stack, &lex); break; case JSON_PARSE_ARRAY_NEXT: if (lex.token_type != JSON_VALUE_INVALID) report_parse_error(stack, &lex); else if (lex.token_start[0] == ']') op = JSON_STACKOP_POP; else if (lex.token_start[0] == ',') op = JSON_STACKOP_PUSH; else report_parse_error(stack, &lex); break; case JSON_PARSE_OBJECT_START: if (lex.token_type == JSON_VALUE_STRING) stack->state = JSON_PARSE_OBJECT_LABEL; else if (lex.token_type == JSON_VALUE_INVALID && lex.token_start[0] == '}') op = JSON_STACKOP_POP; else report_parse_error(stack, &lex); break; case JSON_PARSE_OBJECT_LABEL: if (lex.token_type == JSON_VALUE_INVALID && lex.token_start[0] == ':') { stack->state = JSON_PARSE_OBJECT_NEXT; op = JSON_STACKOP_PUSH; } else report_parse_error(stack, &lex); break; case JSON_PARSE_OBJECT_NEXT: if (lex.token_type != JSON_VALUE_INVALID) report_parse_error(stack, &lex); else if (lex.token_start[0] == '}') op = JSON_STACKOP_POP; else if (lex.token_start[0] == ',') stack->state = JSON_PARSE_OBJECT_COMMA; else report_parse_error(stack, &lex); break; case JSON_PARSE_OBJECT_COMMA: if (lex.token_type == JSON_VALUE_STRING) stack->state = JSON_PARSE_OBJECT_LABEL; else report_parse_error(stack, &lex); break; default: elog(ERROR, "unexpected json parse state: %d", (int) stack->state); } /* Push or pop the stack, if needed. */ switch (op) { case JSON_STACKOP_PUSH: case JSON_STACKOP_PUSH_WITH_PUSHBACK: ++stack; if (stack >= &stacktop[stacksize]) { int stackoffset = stack - stacktop; stacksize = stacksize + 32; stacktop = repalloc(stacktop, sizeof(JsonParseStack) * stacksize); stack = stacktop + stackoffset; } stack->state = JSON_PARSE_VALUE; if (op == JSON_STACKOP_PUSH_WITH_PUSHBACK) goto redo; break; case JSON_STACKOP_POP: if (stack == stacktop) { /* Expect end of input. */ json_lex(&lex); if (lex.token_start != NULL) report_parse_error(NULL, &lex); return; } --stack; break; case JSON_STACKOP_NONE: /* nothing to do */ break; } } }
/* * AggregateCreate */ Oid AggregateCreate(const char *aggName, Oid aggNamespace, int numArgs, oidvector *parameterTypes, Datum allParameterTypes, Datum parameterModes, Datum parameterNames, List *parameterDefaults, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, Oid aggTransType, const char *agginitval) { Relation aggdesc; HeapTuple tup; bool nulls[Natts_pg_aggregate]; Datum values[Natts_pg_aggregate]; Form_pg_proc proc; Oid transfn; Oid finalfn = InvalidOid; /* can be omitted */ Oid sortop = InvalidOid; /* can be omitted */ Oid *aggArgTypes = parameterTypes->values; bool hasPolyArg; bool hasInternalArg; Oid rettype; Oid finaltype; Oid *fnArgs; int nargs_transfn; Oid procOid; TupleDesc tupDesc; int i; ObjectAddress myself, referenced; AclResult aclresult; /* sanity checks (caller should have caught these) */ if (!aggName) elog(ERROR, "no aggregate name supplied"); if (!aggtransfnName) elog(ERROR, "aggregate must have a transition function"); /* check for polymorphic and INTERNAL arguments */ hasPolyArg = false; hasInternalArg = false; for (i = 0; i < numArgs; i++) { if (IsPolymorphicType(aggArgTypes[i])) hasPolyArg = true; else if (aggArgTypes[i] == INTERNALOID) hasInternalArg = true; } /* * If transtype is polymorphic, must have polymorphic argument also; else * we will have no way to deduce the actual transtype. */ if (IsPolymorphicType(aggTransType) && !hasPolyArg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine transition data type"), errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument."))); /* find the transfn */ nargs_transfn = numArgs + 1; fnArgs = (Oid *) palloc(nargs_transfn * sizeof(Oid)); fnArgs[0] = aggTransType; memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid)); transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs, &rettype); /* * Return type of transfn (possibly after refinement by * enforce_generic_type_consistency, if transtype isn't polymorphic) must * exactly match declared transtype. * * In the non-polymorphic-transtype case, it might be okay to allow a * rettype that's binary-coercible to transtype, but I'm not quite * convinced that it's either safe or useful. When transtype is * polymorphic we *must* demand exact equality. */ if (rettype != aggTransType) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("return type of transition function %s is not %s", NameListToString(aggtransfnName), format_type_be(aggTransType)))); tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn)); if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for function %u", transfn); proc = (Form_pg_proc) GETSTRUCT(tup); /* * If the transfn is strict and the initval is NULL, make sure first input * type and transtype are the same (or at least binary-compatible), so * that it's OK to use the first input value as the initial transValue. */ if (proc->proisstrict && agginitval == NULL) { if (numArgs < 1 || !IsBinaryCoercible(aggArgTypes[0], aggTransType)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type"))); } ReleaseSysCache(tup); /* handle finalfn, if supplied */ if (aggfinalfnName) { fnArgs[0] = aggTransType; finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs, &finaltype); } else { /* * If no finalfn, aggregate result type is type of the state value */ finaltype = aggTransType; } Assert(OidIsValid(finaltype)); /* * If finaltype (i.e. aggregate return type) is polymorphic, inputs must * be polymorphic also, else parser will fail to deduce result type. * (Note: given the previous test on transtype and inputs, this cannot * happen, unless someone has snuck a finalfn definition into the catalogs * that itself violates the rule against polymorphic result with no * polymorphic input.) */ if (IsPolymorphicType(finaltype) && !hasPolyArg) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot determine result data type"), errdetail("An aggregate returning a polymorphic type " "must have at least one polymorphic argument."))); /* * Also, the return type can't be INTERNAL unless there's at least one * INTERNAL argument. This is the same type-safety restriction we enforce * for regular functions, but at the level of aggregates. We must test * this explicitly because we allow INTERNAL as the transtype. */ if (finaltype == INTERNALOID && !hasInternalArg) 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."))); /* handle sortop, if supplied */ if (aggsortopName) { if (numArgs != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("sort operator can only be specified for single-argument aggregates"))); sortop = LookupOperName(NULL, aggsortopName, aggArgTypes[0], aggArgTypes[0], false, -1); } /* * permission checks on used types */ for (i = 0; i < numArgs; i++) { aclresult = pg_type_aclcheck(aggArgTypes[i], GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error_type(aclresult, aggArgTypes[i]); } aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error_type(aclresult, aggTransType); aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error_type(aclresult, finaltype); /* * Everything looks okay. Try to create the pg_proc entry for the * aggregate. (This could fail if there's already a conflicting entry.) */ procOid = ProcedureCreate(aggName, aggNamespace, false, /* no replacement */ false, /* doesn't return a set */ finaltype, /* returnType */ GetUserId(), /* proowner */ INTERNALlanguageId, /* languageObjectId */ InvalidOid, /* no validator */ "aggregate_dummy", /* placeholder proc */ NULL, /* probin */ true, /* isAgg */ false, /* isWindowFunc */ false, /* security invoker (currently not * definable for agg) */ false, /* isLeakProof */ false, /* isStrict (not needed for agg) */ PROVOLATILE_IMMUTABLE, /* volatility (not * needed for agg) */ parameterTypes, /* paramTypes */ allParameterTypes, /* allParamTypes */ parameterModes, /* parameterModes */ parameterNames, /* parameterNames */ parameterDefaults, /* parameterDefaults */ PointerGetDatum(NULL), /* proconfig */ 1, /* procost */ 0); /* prorows */ /* * Okay to create the pg_aggregate entry. */ /* initialize nulls and values */ for (i = 0; i < Natts_pg_aggregate; i++) { nulls[i] = false; values[i] = (Datum) NULL; } values[Anum_pg_aggregate_aggfnoid - 1] = ObjectIdGetDatum(procOid); values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn); values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn); values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop); values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType); if (agginitval) values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval); else nulls[Anum_pg_aggregate_agginitval - 1] = true; aggdesc = heap_open(AggregateRelationId, RowExclusiveLock); tupDesc = aggdesc->rd_att; tup = heap_form_tuple(tupDesc, values, nulls); simple_heap_insert(aggdesc, tup); CatalogUpdateIndexes(aggdesc, tup); heap_close(aggdesc, RowExclusiveLock); /* * Create dependencies for the aggregate (above and beyond those already * made by ProcedureCreate). Note: we don't need an explicit dependency * on aggTransType since we depend on it indirectly through transfn. */ myself.classId = ProcedureRelationId; myself.objectId = procOid; myself.objectSubId = 0; /* Depends on transition function */ referenced.classId = ProcedureRelationId; referenced.objectId = transfn; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); /* Depends on final function, if any */ if (OidIsValid(finalfn)) { referenced.classId = ProcedureRelationId; referenced.objectId = finalfn; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } /* Depends on sort operator, if any */ if (OidIsValid(sortop)) { referenced.classId = OperatorRelationId; referenced.objectId = sortop; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } return procOid; }
Datum pg_freespacemap_relations(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; Datum result; MemoryContext oldcontext; FreeSpaceRelationsContext *fctx; /* User function context. */ TupleDesc tupledesc; HeapTuple tuple; FSMHeader *FreeSpaceMap; /* FSM main structure. */ FSMRelation *fsmrel; /* Individual relation. */ if (SRF_IS_FIRSTCALL()) { int i; int numRelations; /* Max no. of Relations in map. */ /* * Get the free space map data structure. */ FreeSpaceMap = GetFreeSpaceMap(); numRelations = MaxFSMRelations; funcctx = SRF_FIRSTCALL_INIT(); /* Switch context when allocating stuff to be used in later calls */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* * Create a function context for cross-call persistence. */ fctx = (FreeSpaceRelationsContext *) palloc(sizeof(FreeSpaceRelationsContext)); funcctx->user_fctx = fctx; /* Construct a tuple descriptor for the result rows. */ tupledesc = CreateTemplateTupleDesc(NUM_FREESPACE_RELATIONS_ELEM, false); TupleDescInitEntry(tupledesc, (AttrNumber) 1, "reltablespace", OIDOID, -1, 0); TupleDescInitEntry(tupledesc, (AttrNumber) 2, "reldatabase", OIDOID, -1, 0); TupleDescInitEntry(tupledesc, (AttrNumber) 3, "relfilenode", OIDOID, -1, 0); TupleDescInitEntry(tupledesc, (AttrNumber) 4, "avgrequest", INT4OID, -1, 0); TupleDescInitEntry(tupledesc, (AttrNumber) 5, "interestingpages", INT4OID, -1, 0); TupleDescInitEntry(tupledesc, (AttrNumber) 6, "storedpages", INT4OID, -1, 0); TupleDescInitEntry(tupledesc, (AttrNumber) 7, "nextpage", INT4OID, -1, 0); fctx->tupdesc = BlessTupleDesc(tupledesc); /* * Allocate numRelations worth of FreeSpaceRelationsRec records, this * is also an upper bound. */ fctx->record = (FreeSpaceRelationsRec *) palloc(sizeof(FreeSpaceRelationsRec) * numRelations); /* Return to original context when allocating transient memory */ MemoryContextSwitchTo(oldcontext); /* * Lock free space map and scan though all the relations. */ LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE); i = 0; for (fsmrel = FreeSpaceMap->usageList; fsmrel; fsmrel = fsmrel->nextUsage) { fctx->record[i].reltablespace = fsmrel->key.spcNode; fctx->record[i].reldatabase = fsmrel->key.dbNode; fctx->record[i].relfilenode = fsmrel->key.relNode; fctx->record[i].avgrequest = (int64) fsmrel->avgRequest; fctx->record[i].interestingpages = fsmrel->interestingPages; fctx->record[i].storedpages = fsmrel->storedPages; fctx->record[i].nextpage = fsmrel->nextPage; fctx->record[i].isindex = fsmrel->isIndex; i++; } /* Release free space map. */ LWLockRelease(FreeSpaceLock); /* Set the real no. of calls as we know it now! */ Assert(i <= numRelations); funcctx->max_calls = i; } funcctx = SRF_PERCALL_SETUP(); /* Get the saved state */ fctx = funcctx->user_fctx; if (funcctx->call_cntr < funcctx->max_calls) { int i = funcctx->call_cntr; FreeSpaceRelationsRec *record = &fctx->record[i]; Datum values[NUM_FREESPACE_RELATIONS_ELEM]; bool nulls[NUM_FREESPACE_RELATIONS_ELEM]; values[0] = ObjectIdGetDatum(record->reltablespace); nulls[0] = false; values[1] = ObjectIdGetDatum(record->reldatabase); nulls[1] = false; values[2] = ObjectIdGetDatum(record->relfilenode); nulls[2] = false; /* * avgrequest isn't meaningful for an index */ if (record->isindex) { nulls[3] = true; } else { values[3] = UInt32GetDatum(record->avgrequest); nulls[3] = false; } values[4] = Int32GetDatum(record->interestingpages); nulls[4] = false; values[5] = Int32GetDatum(record->storedpages); nulls[5] = false; values[6] = Int32GetDatum(record->nextpage); nulls[6] = false; /* Build and return the tuple. */ tuple = heap_form_tuple(fctx->tupdesc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } else SRF_RETURN_DONE(funcctx); }
/* * array_typanalyze -- typanalyze function for array columns */ Datum array_typanalyze(PG_FUNCTION_ARGS) { VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0); Oid element_typeid; TypeCacheEntry *typentry; ArrayAnalyzeExtraData *extra_data; /* * Call the standard typanalyze function. It may fail to find needed * operators, in which case we also can't do anything, so just fail. */ if (!std_typanalyze(stats)) PG_RETURN_BOOL(false); /* * Check attribute data type is a varlena array (or a domain over one). */ element_typeid = get_base_element_type(stats->attrtypid); if (!OidIsValid(element_typeid)) elog(ERROR, "array_typanalyze was invoked for non-array type %u", stats->attrtypid); /* * Gather information about the element type. If we fail to find * something, return leaving the state from std_typanalyze() in place. */ typentry = lookup_type_cache(element_typeid, TYPECACHE_EQ_OPR | TYPECACHE_CMP_PROC_FINFO | TYPECACHE_HASH_PROC_FINFO); if (!OidIsValid(typentry->eq_opr) || !OidIsValid(typentry->cmp_proc_finfo.fn_oid) || !OidIsValid(typentry->hash_proc_finfo.fn_oid)) PG_RETURN_BOOL(true); /* Store our findings for use by compute_array_stats() */ extra_data = (ArrayAnalyzeExtraData *) palloc(sizeof(ArrayAnalyzeExtraData)); extra_data->type_id = typentry->type_id; extra_data->eq_opr = typentry->eq_opr; extra_data->typbyval = typentry->typbyval; extra_data->typlen = typentry->typlen; extra_data->typalign = typentry->typalign; extra_data->cmp = &typentry->cmp_proc_finfo; extra_data->hash = &typentry->hash_proc_finfo; /* Save old compute_stats and extra_data for scalar statistics ... */ extra_data->std_compute_stats = stats->compute_stats; extra_data->std_extra_data = stats->extra_data; /* ... and replace with our info */ stats->compute_stats = compute_array_stats; stats->extra_data = extra_data; /* * Note we leave stats->minrows set as std_typanalyze set it. Should it * be increased for array analysis purposes? */ PG_RETURN_BOOL(true); }
Datum pg_freespacemap_pages(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; Datum result; MemoryContext oldcontext; FreeSpacePagesContext *fctx; /* User function context. */ TupleDesc tupledesc; HeapTuple tuple; FSMHeader *FreeSpaceMap; /* FSM main structure. */ FSMRelation *fsmrel; /* Individual relation. */ if (SRF_IS_FIRSTCALL()) { int i; int nchunks; /* Size of freespace.c's arena. */ int numPages; /* Max possible no. of pages in map. */ int nPages; /* Mapped pages for a relation. */ /* * Get the free space map data structure. */ FreeSpaceMap = GetFreeSpaceMap(); /* this must match calculation in InitFreeSpaceMap(): */ nchunks = (MaxFSMPages - 1) / CHUNKPAGES + 1; /* Worst case (lots of indexes) could have this many pages: */ numPages = nchunks * INDEXCHUNKPAGES; funcctx = SRF_FIRSTCALL_INIT(); /* Switch context when allocating stuff to be used in later calls */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* * Create a function context for cross-call persistence. */ fctx = (FreeSpacePagesContext *) palloc(sizeof(FreeSpacePagesContext)); funcctx->user_fctx = fctx; /* Construct a tuple descriptor for the result rows. */ tupledesc = CreateTemplateTupleDesc(NUM_FREESPACE_PAGES_ELEM, false); TupleDescInitEntry(tupledesc, (AttrNumber) 1, "reltablespace", OIDOID, -1, 0); TupleDescInitEntry(tupledesc, (AttrNumber) 2, "reldatabase", OIDOID, -1, 0); TupleDescInitEntry(tupledesc, (AttrNumber) 3, "relfilenode", OIDOID, -1, 0); TupleDescInitEntry(tupledesc, (AttrNumber) 4, "relblocknumber", INT8OID, -1, 0); TupleDescInitEntry(tupledesc, (AttrNumber) 5, "bytes", INT4OID, -1, 0); fctx->tupdesc = BlessTupleDesc(tupledesc); /* * Allocate numPages worth of FreeSpacePagesRec records, this is an * upper bound. */ fctx->record = (FreeSpacePagesRec *) palloc(sizeof(FreeSpacePagesRec) * numPages); /* Return to original context when allocating transient memory */ MemoryContextSwitchTo(oldcontext); /* * Lock free space map and scan though all the relations. For each * relation, gets all its mapped pages. */ LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE); i = 0; for (fsmrel = FreeSpaceMap->usageList; fsmrel; fsmrel = fsmrel->nextUsage) { if (fsmrel->isIndex) { /* Index relation. */ IndexFSMPageData *page; page = (IndexFSMPageData *) (FreeSpaceMap->arena + fsmrel->firstChunk * CHUNKBYTES); for (nPages = 0; nPages < fsmrel->storedPages; nPages++) { fctx->record[i].reltablespace = fsmrel->key.spcNode; fctx->record[i].reldatabase = fsmrel->key.dbNode; fctx->record[i].relfilenode = fsmrel->key.relNode; fctx->record[i].relblocknumber = IndexFSMPageGetPageNum(page); fctx->record[i].bytes = 0; fctx->record[i].isindex = true; page++; i++; } } else { /* Heap relation. */ FSMPageData *page; page = (FSMPageData *) (FreeSpaceMap->arena + fsmrel->firstChunk * CHUNKBYTES); for (nPages = 0; nPages < fsmrel->storedPages; nPages++) { fctx->record[i].reltablespace = fsmrel->key.spcNode; fctx->record[i].reldatabase = fsmrel->key.dbNode; fctx->record[i].relfilenode = fsmrel->key.relNode; fctx->record[i].relblocknumber = FSMPageGetPageNum(page); fctx->record[i].bytes = FSMPageGetSpace(page); fctx->record[i].isindex = false; page++; i++; } } } /* Release free space map. */ LWLockRelease(FreeSpaceLock); /* Set the real no. of calls as we know it now! */ Assert(i <= numPages); funcctx->max_calls = i; } funcctx = SRF_PERCALL_SETUP(); /* Get the saved state */ fctx = funcctx->user_fctx; if (funcctx->call_cntr < funcctx->max_calls) { int i = funcctx->call_cntr; FreeSpacePagesRec *record = &fctx->record[i]; Datum values[NUM_FREESPACE_PAGES_ELEM]; bool nulls[NUM_FREESPACE_PAGES_ELEM]; values[0] = ObjectIdGetDatum(record->reltablespace); nulls[0] = false; values[1] = ObjectIdGetDatum(record->reldatabase); nulls[1] = false; values[2] = ObjectIdGetDatum(record->relfilenode); nulls[2] = false; values[3] = Int64GetDatum((int64) record->relblocknumber); nulls[3] = false; /* * Set (free) bytes to NULL for an index relation. */ if (record->isindex) { nulls[4] = true; } else { values[4] = UInt32GetDatum(record->bytes); nulls[4] = false; } /* Build and return the tuple. */ tuple = heap_form_tuple(fctx->tupdesc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } else SRF_RETURN_DONE(funcctx); }
/* ---------------------------------------------------------------- * ExecGather(node) * * Scans the relation via multiple workers and returns * the next qualifying tuple. * ---------------------------------------------------------------- */ static TupleTableSlot * ExecGather(PlanState *pstate) { GatherState *node = castNode(GatherState, pstate); TupleTableSlot *slot; ExprContext *econtext; CHECK_FOR_INTERRUPTS(); /* * Initialize the parallel context and workers on first execution. We do * this on first execution rather than during node initialization, as it * needs to allocate a large dynamic segment, so it is better to do it * only if it is really needed. */ if (!node->initialized) { EState *estate = node->ps.state; Gather *gather = (Gather *) node->ps.plan; /* * Sometimes we might have to run without parallelism; but if parallel * mode is active then we can try to fire up some workers. */ if (gather->num_workers > 0 && estate->es_use_parallel_mode) { ParallelContext *pcxt; /* Initialize, or re-initialize, shared state needed by workers. */ if (!node->pei) node->pei = ExecInitParallelPlan(node->ps.lefttree, estate, gather->initParam, gather->num_workers, node->tuples_needed); else ExecParallelReinitialize(node->ps.lefttree, node->pei, gather->initParam); /* * Register backend workers. We might not get as many as we * requested, or indeed any at all. */ pcxt = node->pei->pcxt; LaunchParallelWorkers(pcxt); /* We save # workers launched for the benefit of EXPLAIN */ node->nworkers_launched = pcxt->nworkers_launched; /* Set up tuple queue readers to read the results. */ if (pcxt->nworkers_launched > 0) { ExecParallelCreateReaders(node->pei); /* Make a working array showing the active readers */ node->nreaders = pcxt->nworkers_launched; node->reader = (TupleQueueReader **) palloc(node->nreaders * sizeof(TupleQueueReader *)); memcpy(node->reader, node->pei->reader, node->nreaders * sizeof(TupleQueueReader *)); } else { /* No workers? Then never mind. */ node->nreaders = 0; node->reader = NULL; } node->nextreader = 0; } /* Run plan locally if no workers or enabled and not single-copy. */ node->need_to_scan_locally = (node->nreaders == 0) || (!gather->single_copy && parallel_leader_participation); node->initialized = true; } /* * Reset per-tuple memory context to free any expression evaluation * storage allocated in the previous tuple cycle. */ econtext = node->ps.ps_ExprContext; ResetExprContext(econtext); /* * Get next tuple, either from one of our workers, or by running the plan * ourselves. */ slot = gather_getnext(node); if (TupIsNull(slot)) return NULL; /* If no projection is required, we're done. */ if (node->ps.ps_ProjInfo == NULL) return slot; /* * Form the result tuple using ExecProject(), and return it. */ econtext->ecxt_outertuple = slot; return ExecProject(node->ps.ps_ProjInfo); }
/* * Write WAL record of a page split. */ XLogRecPtr gistXLogSplit(RelFileNode node, BlockNumber blkno, bool page_is_leaf, SplitedPageLayout *dist, BlockNumber origrlink, GistNSN orignsn, Buffer leftchildbuf, bool markfollowright) { XLogRecData *rdata; gistxlogPageSplit xlrec; SplitedPageLayout *ptr; int npage = 0, cur; XLogRecPtr recptr; for (ptr = dist; ptr; ptr = ptr->next) npage++; rdata = (XLogRecData *) palloc(sizeof(XLogRecData) * (npage * 2 + 2)); xlrec.node = node; xlrec.origblkno = blkno; xlrec.origrlink = origrlink; xlrec.orignsn = orignsn; xlrec.origleaf = page_is_leaf; xlrec.npage = (uint16) npage; xlrec.leftchild = BufferIsValid(leftchildbuf) ? BufferGetBlockNumber(leftchildbuf) : InvalidBlockNumber; xlrec.markfollowright = markfollowright; rdata[0].data = (char *) &xlrec; rdata[0].len = sizeof(gistxlogPageSplit); rdata[0].buffer = InvalidBuffer; cur = 1; /* * Include a full page image of the child buf. (only necessary if a * checkpoint happened since the child page was split) */ if (BufferIsValid(leftchildbuf)) { rdata[cur - 1].next = &(rdata[cur]); rdata[cur].data = NULL; rdata[cur].len = 0; rdata[cur].buffer = leftchildbuf; rdata[cur].buffer_std = true; cur++; } for (ptr = dist; ptr; ptr = ptr->next) { rdata[cur - 1].next = &(rdata[cur]); rdata[cur].buffer = InvalidBuffer; rdata[cur].data = (char *) &(ptr->block); rdata[cur].len = sizeof(gistxlogPage); cur++; rdata[cur - 1].next = &(rdata[cur]); rdata[cur].buffer = InvalidBuffer; rdata[cur].data = (char *) (ptr->list); rdata[cur].len = ptr->lenlist; cur++; } rdata[cur - 1].next = NULL; recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_SPLIT, rdata); pfree(rdata); return recptr; }
/* * Connect to remote server using specified server and user mapping properties. */ static PGconn * connect_pg_server(ForeignServer *server, UserMapping *user) { PGconn *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 "postgres_fdw" as fallback_application_name. */ keywords[n] = "fallback_application_name"; values[n] = "postgres_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 = PQconnectdbParams(keywords, values, false); if (!conn || PQstatus(conn) != CONNECTION_OK) ereport(ERROR, (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), errmsg("could not connect to server \"%s\"", server->servername), errdetail_internal("%s", pchomp(PQerrorMessage(conn))))); /* * Check that non-superuser has used password to establish connection; * otherwise, he's piggybacking on the postgres server's user * identity. See also dblink_security_check() in contrib/dblink. */ if (!superuser() && !PQconnectionUsedPassword(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."))); /* Prepare new session for use */ configure_remote_session(conn); pfree(keywords); pfree(values); } PG_CATCH(); { /* Release PGconn data structure if we managed to create one */ if (conn) PQfinish(conn); PG_RE_THROW(); } PG_END_TRY(); return conn; }
/* * Write XLOG record describing a page update. The update can include any * number of deletions and/or insertions of tuples on a single index page. * * If this update inserts a downlink for a split page, also record that * the F_FOLLOW_RIGHT flag on the child page is cleared and NSN set. * * Note that both the todelete array and the tuples are marked as belonging * to the target buffer; they need not be stored in XLOG if XLogInsert decides * to log the whole buffer contents instead. Also, we take care that there's * at least one rdata item referencing the buffer, even when ntodelete and * ituplen are both zero; this ensures that XLogInsert knows about the buffer. */ XLogRecPtr gistXLogUpdate(RelFileNode node, Buffer buffer, OffsetNumber *todelete, int ntodelete, IndexTuple *itup, int ituplen, Buffer leftchildbuf) { XLogRecData *rdata; gistxlogPageUpdate xlrec; int cur, i; XLogRecPtr recptr; rdata = (XLogRecData *) palloc(sizeof(XLogRecData) * (3 + ituplen)); xlrec.node = node; xlrec.blkno = BufferGetBlockNumber(buffer); xlrec.ntodelete = ntodelete; xlrec.leftchild = BufferIsValid(leftchildbuf) ? BufferGetBlockNumber(leftchildbuf) : InvalidBlockNumber; rdata[0].data = (char *) &xlrec; rdata[0].len = sizeof(gistxlogPageUpdate); rdata[0].buffer = InvalidBuffer; rdata[0].next = &(rdata[1]); rdata[1].data = (char *) todelete; rdata[1].len = sizeof(OffsetNumber) * ntodelete; rdata[1].buffer = buffer; rdata[1].buffer_std = true; cur = 2; /* new tuples */ for (i = 0; i < ituplen; i++) { rdata[cur - 1].next = &(rdata[cur]); rdata[cur].data = (char *) (itup[i]); rdata[cur].len = IndexTupleSize(itup[i]); rdata[cur].buffer = buffer; rdata[cur].buffer_std = true; cur++; } /* * Include a full page image of the child buf. (only necessary if a * checkpoint happened since the child page was split) */ if (BufferIsValid(leftchildbuf)) { rdata[cur - 1].next = &(rdata[cur]); rdata[cur].data = NULL; rdata[cur].len = 0; rdata[cur].buffer = leftchildbuf; rdata[cur].buffer_std = true; cur++; } rdata[cur - 1].next = NULL; recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_UPDATE, rdata); pfree(rdata); return recptr; }
Datum xslt_process(PG_FUNCTION_ARGS) { #ifdef USE_LIBXSLT text *doct = PG_GETARG_TEXT_P(0); text *ssheet = PG_GETARG_TEXT_P(1); text *paramstr; const char **params; xsltStylesheetPtr stylesheet = NULL; xmlDocPtr doctree; xmlDocPtr restree; xmlDocPtr ssdoc = NULL; xmlChar *resstr; int resstat; int reslen; if (fcinfo->nargs == 3) { paramstr = PG_GETARG_TEXT_P(2); params = parse_params(paramstr); } else { /* No parameters */ params = (const char **) palloc(sizeof(char *)); params[0] = NULL; } /* Setup parser */ pgxml_parser_init(); /* Check to see if document is a file or a literal */ if (VARDATA(doct)[0] == '<') doctree = xmlParseMemory((char *) VARDATA(doct), VARSIZE(doct) - VARHDRSZ); else doctree = xmlParseFile(text_to_cstring(doct)); if (doctree == NULL) xml_ereport(ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, "error parsing XML document"); /* Same for stylesheet */ if (VARDATA(ssheet)[0] == '<') { ssdoc = xmlParseMemory((char *) VARDATA(ssheet), VARSIZE(ssheet) - VARHDRSZ); if (ssdoc == NULL) { xmlFreeDoc(doctree); xml_ereport(ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, "error parsing stylesheet as XML document"); } stylesheet = xsltParseStylesheetDoc(ssdoc); } else stylesheet = xsltParseStylesheetFile((xmlChar *) text_to_cstring(ssheet)); if (stylesheet == NULL) { xmlFreeDoc(doctree); xsltCleanupGlobals(); xml_ereport(ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, "failed to parse stylesheet"); } restree = xsltApplyStylesheet(stylesheet, doctree, params); resstat = xsltSaveResultToString(&resstr, &reslen, restree, stylesheet); xsltFreeStylesheet(stylesheet); xmlFreeDoc(restree); xmlFreeDoc(doctree); xsltCleanupGlobals(); if (resstat < 0) PG_RETURN_NULL(); PG_RETURN_TEXT_P(cstring_to_text_with_len((char *) resstr, reslen)); #else /* !USE_LIBXSLT */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("xslt_process() is not available without libxslt"))); PG_RETURN_NULL(); #endif /* USE_LIBXSLT */ }
/* Either all the labels must be NULL, or none. */ node = SGITNODEPTR(innerTuple); if (IndexTupleHasNulls(node)) { SGITITERATE(innerTuple, i, node) { if (!IndexTupleHasNulls(node)) elog(ERROR, "some but not all node labels are null in SPGiST inner tuple"); } /* They're all null, so just return NULL */ return NULL; } else { nodeLabels = (Datum *) palloc(sizeof(Datum) * innerTuple->nNodes); SGITITERATE(innerTuple, i, node) { if (IndexTupleHasNulls(node)) elog(ERROR, "some but not all node labels are null in SPGiST inner tuple"); nodeLabels[i] = SGNTDATUM(node, state); } return nodeLabels; } } /* * Add a new item to the page, replacing a PLACEHOLDER item if possible. * Return the location it's inserted at, or InvalidOffsetNumber on failure. * * If startOffset isn't NULL, we start searching for placeholders at
/* * get_relation_info - * Retrieves catalog information for a given relation. * * Given the Oid of the relation, return the following info into fields * of the RelOptInfo struct: * * min_attr lowest valid AttrNumber * max_attr highest valid AttrNumber * indexlist list of IndexOptInfos for relation's indexes * fdwroutine if it's a foreign table, the FDW function pointers * pages number of pages * tuples number of tuples * * Also, initialize the attr_needed[] and attr_widths[] arrays. In most * cases these are left as zeroes, but sometimes we need to compute attr * widths here, and we may as well cache the results for costsize.c. * * If inhparent is true, all we need to do is set up the attr arrays: * the RelOptInfo actually represents the appendrel formed by an inheritance * tree, and so the parent rel's physical size and index information isn't * important for it. */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ relation = heap_open(relationObjectId, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot access temporary or unlogged relations during recovery"))); rel->min_attr = FirstLowInvalidHeapAttributeNumber + 1; rel->max_attr = RelationGetNumberOfAttributes(relation); rel->reltablespace = RelationGetForm(relation)->reltablespace; Assert(rel->max_attr >= rel->min_attr); rel->attr_needed = (Relids *) palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids)); rel->attr_widths = (int32 *) palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(int32)); /* * Estimate relation size --- unless it's an inheritance parent, in which * case the size will be computed later in set_append_rel_pathlist, and we * must leave it zero for now to avoid bollixing the total_table_pages * calculation. */ if (!inhparent) estimate_rel_size(relation, rel->attr_widths - rel->min_attr, &rel->pages, &rel->tuples, &rel->allvisfrac); /* * Make list of indexes. Ignore indexes on system catalogs if told to. * Don't bother with indexes for an inheritance parent, either. */ if (inhparent || (IgnoreSystemIndexes && IsSystemRelation(relation))) hasindex = false; else hasindex = relation->rd_rel->relhasindex; if (hasindex) { List *indexoidlist; ListCell *l; LOCKMODE lmode; indexoidlist = RelationGetIndexList(relation); /* * For each index, we get the same type of lock that the executor will * need, and do not release it. This saves a couple of trips to the * shared lock manager while not creating any real loss of * concurrency, because no schema changes could be happening on the * index while we hold lock on the parent rel, and neither lock type * blocks any other kind of index operation. */ if (rel->relid == root->parse->resultRelation) lmode = RowExclusiveLock; else lmode = AccessShareLock; foreach(l, indexoidlist) { Oid indexoid = lfirst_oid(l); Relation indexRelation; Form_pg_index index; IndexOptInfo *info; int ncolumns; int i; /* * Extract info from the relation descriptor for the index. */ indexRelation = index_open(indexoid, lmode); index = indexRelation->rd_index; /* * Ignore invalid indexes, since they can't safely be used for * queries. Note that this is OK because the data structure we * are constructing is only used by the planner --- the executor * still needs to insert into "invalid" indexes, if they're marked * IndexIsReady. */ if (!IndexIsValid(index)) { index_close(indexRelation, NoLock); continue; } /* * If the index is valid, but cannot yet be used, ignore it; but * mark the plan we are generating as transient. See * src/backend/access/heap/README.HOT for discussion. */ if (index->indcheckxmin && !TransactionIdPrecedes(HeapTupleHeaderGetXmin(indexRelation->rd_indextuple->t_data), TransactionXmin)) { root->glob->transientPlan = true; index_close(indexRelation, NoLock); continue; } info = makeNode(IndexOptInfo); info->indexoid = index->indexrelid; info->reltablespace = RelationGetForm(indexRelation)->reltablespace; info->rel = rel; info->ncolumns = ncolumns = index->indnatts; info->indexkeys = (int *) palloc(sizeof(int) * ncolumns); info->indexcollations = (Oid *) palloc(sizeof(Oid) * ncolumns); info->opfamily = (Oid *) palloc(sizeof(Oid) * ncolumns); info->opcintype = (Oid *) palloc(sizeof(Oid) * ncolumns); for (i = 0; i < ncolumns; i++) { info->indexkeys[i] = index->indkey.values[i]; info->indexcollations[i] = indexRelation->rd_indcollation[i]; info->opfamily[i] = indexRelation->rd_opfamily[i]; info->opcintype[i] = indexRelation->rd_opcintype[i]; } info->relam = indexRelation->rd_rel->relam; info->amcostestimate = indexRelation->rd_am->amcostestimate; info->canreturn = index_can_return(indexRelation); info->amcanorderbyop = indexRelation->rd_am->amcanorderbyop; info->amoptionalkey = indexRelation->rd_am->amoptionalkey; info->amsearcharray = indexRelation->rd_am->amsearcharray; info->amsearchnulls = indexRelation->rd_am->amsearchnulls; info->amhasgettuple = OidIsValid(indexRelation->rd_am->amgettuple); info->amhasgetbitmap = OidIsValid(indexRelation->rd_am->amgetbitmap); /* * Fetch the ordering information for the index, if any. */ if (info->relam == BTREE_AM_OID) { /* * If it's a btree index, we can use its opfamily OIDs * directly as the sort ordering opfamily OIDs. */ Assert(indexRelation->rd_am->amcanorder); info->sortopfamily = info->opfamily; info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns); info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns); for (i = 0; i < ncolumns; i++) { int16 opt = indexRelation->rd_indoption[i]; info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0; info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0; } } else if (indexRelation->rd_am->amcanorder) { /* * Otherwise, identify the corresponding btree opfamilies by * trying to map this index's "<" operators into btree. Since * "<" uniquely defines the behavior of a sort order, this is * a sufficient test. * * XXX This method is rather slow and also requires the * undesirable assumption that the other index AM numbers its * strategies the same as btree. It'd be better to have a way * to explicitly declare the corresponding btree opfamily for * each opfamily of the other index type. But given the lack * of current or foreseeable amcanorder index types, it's not * worth expending more effort on now. */ info->sortopfamily = (Oid *) palloc(sizeof(Oid) * ncolumns); info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns); info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns); for (i = 0; i < ncolumns; i++) { int16 opt = indexRelation->rd_indoption[i]; Oid ltopr; Oid btopfamily; Oid btopcintype; int16 btstrategy; info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0; info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0; ltopr = get_opfamily_member(info->opfamily[i], info->opcintype[i], info->opcintype[i], BTLessStrategyNumber); if (OidIsValid(ltopr) && get_ordering_op_properties(ltopr, &btopfamily, &btopcintype, &btstrategy) && btopcintype == info->opcintype[i] && btstrategy == BTLessStrategyNumber) { /* Successful mapping */ info->sortopfamily[i] = btopfamily; } else { /* Fail ... quietly treat index as unordered */ info->sortopfamily = NULL; info->reverse_sort = NULL; info->nulls_first = NULL; break; } } } else { info->sortopfamily = NULL; info->reverse_sort = NULL; info->nulls_first = NULL; } /* * Fetch the index expressions and predicate, if any. We must * modify the copies we obtain from the relcache to have the * correct varno for the parent relation, so that they match up * correctly against qual clauses. */ info->indexprs = RelationGetIndexExpressions(indexRelation); info->indpred = RelationGetIndexPredicate(indexRelation); if (info->indexprs && varno != 1) ChangeVarNodes((Node *) info->indexprs, 1, varno, 0); if (info->indpred && varno != 1) ChangeVarNodes((Node *) info->indpred, 1, varno, 0); /* Build targetlist using the completed indexprs data */ info->indextlist = build_index_tlist(root, info, relation); info->predOK = false; /* set later in indxpath.c */ info->unique = index->indisunique; info->immediate = index->indimmediate; info->hypothetical = false; /* * Estimate the index size. If it's not a partial index, we lock * the number-of-tuples estimate to equal the parent table; if it * is partial then we have to use the same methods as we would for * a table, except we can be sure that the index is not larger * than the table. */ if (info->indpred == NIL) { info->pages = RelationGetNumberOfBlocks(indexRelation); info->tuples = rel->tuples; } else { double allvisfrac; /* dummy */ estimate_rel_size(indexRelation, NULL, &info->pages, &info->tuples, &allvisfrac); if (info->tuples > rel->tuples) info->tuples = rel->tuples; } if (info->relam == BTREE_AM_OID) { /* For btrees, get tree height while we have the index open */ info->tree_height = _bt_getrootheight(indexRelation); } else { /* For other index types, just set it to "unknown" for now */ info->tree_height = -1; } index_close(indexRelation, NoLock); indexinfos = lcons(info, indexinfos); } list_free(indexoidlist); }
/* ** GiST Compress and Decompress methods */ Datum g_int_compress(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); GISTENTRY *retval; ArrayType *r; int len; int *dr; int i, min, cand; if (entry->leafkey) { r = (ArrayType *) PG_DETOAST_DATUM_COPY(entry->key); PREPAREARR(r); r->flags |= LEAFKEY; retval = palloc(sizeof(GISTENTRY)); gistentryinit(*retval, PointerGetDatum(r), entry->rel, entry->page, entry->offset, VARSIZE(r), FALSE); PG_RETURN_POINTER(retval); } r = (ArrayType *) PG_DETOAST_DATUM(entry->key); if (ISLEAFKEY(r) || ARRISVOID(r)) { if (r != (ArrayType *) DatumGetPointer(entry->key)) pfree(r); PG_RETURN_POINTER(entry); } if ((len = ARRNELEMS(r)) >= 2 * MAXNUMRANGE) { /* compress */ if (r == (ArrayType *) DatumGetPointer(entry->key)) r = (ArrayType *) PG_DETOAST_DATUM_COPY(entry->key); r = resize_intArrayType(r, 2 * (len)); dr = ARRPTR(r); for (i = len - 1; i >= 0; i--) dr[2 * i] = dr[2 * i + 1] = dr[i]; len *= 2; cand = 1; while (len > MAXNUMRANGE * 2) { min = 0x7fffffff; for (i = 2; i < len; i += 2) if (min > (dr[i] - dr[i - 1])) { min = (dr[i] - dr[i - 1]); cand = i; } memmove((void *) &dr[cand - 1], (void *) &dr[cand + 1], (len - cand - 1) * sizeof(int)); len -= 2; } r = resize_intArrayType(r, len); retval = palloc(sizeof(GISTENTRY)); gistentryinit(*retval, PointerGetDatum(r), entry->rel, entry->page, entry->offset, VARSIZE(r), FALSE); PG_RETURN_POINTER(retval); } else PG_RETURN_POINTER(entry); PG_RETURN_POINTER(entry); }
PGDLLEXPORT Datum kshortest_path(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; TupleDesc tuple_desc; General_path_element_t *path = NULL; size_t result_count = 0; /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* CREATE OR REPLACE FUNCTION _pgr_ksp( sql text, start_vid bigint, end_vid bigint, k integer, directed boolean, heap_paths boolean */ PGR_DBG("Calling process"); compute( text_to_cstring(PG_GETARG_TEXT_P(0)), /* SQL */ PG_GETARG_INT64(1), /* start_vid */ PG_GETARG_INT64(2), /* end_vid */ PG_GETARG_INT32(3), /* k */ PG_GETARG_BOOL(4), /* directed */ PG_GETARG_BOOL(5), /* heap_paths */ &path, &result_count); PGR_DBG("Total number of tuples to be returned %ld \n", result_count); /* */ /**********************************************************************/ #if PGSQL_VERSION > 95 funcctx->max_calls = result_count; #else funcctx->max_calls = (uint32_t)result_count; #endif funcctx->user_fctx = path; if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record\n"))); funcctx->tuple_desc = tuple_desc; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); tuple_desc = funcctx->tuple_desc; path = (General_path_element_t*) funcctx->user_fctx; if (funcctx->call_cntr < funcctx->max_calls) { /* do when there is more left to send */ HeapTuple tuple; Datum result; Datum *values; bool* nulls; values = palloc(7 * sizeof(Datum)); nulls = palloc(7 * sizeof(bool)); size_t i; for (i = 0; i < 7; ++i) { nulls[i] = false; } values[0] = Int32GetDatum(funcctx->call_cntr + 1); values[1] = Int32GetDatum(path[funcctx->call_cntr].start_id + 1); values[2] = Int32GetDatum(path[funcctx->call_cntr].seq); values[3] = Int64GetDatum(path[funcctx->call_cntr].node); values[4] = Int64GetDatum(path[funcctx->call_cntr].edge); values[5] = Float8GetDatum(path[funcctx->call_cntr].cost); values[6] = Float8GetDatum(path[funcctx->call_cntr].agg_cost); tuple = heap_form_tuple(tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } else { /* do when there is no more left */ SRF_RETURN_DONE(funcctx); } }
#endif /* Set up the elfpart structure and allocate an empty symbol table. */ static void new(elfpart *part) { static Elf32_Sym blanksym = { 0, 0, 0, 0, 0, SHN_UNDEF }; Elf32_Sym *sym; part->shtype = SHT_SYMTAB; part->shname = ".symtab"; part->info = 1; part->entsize = sizeof(Elf32_Sym); part->size = part->entsize; part->count = 1; sym = palloc(part); *sym = blanksym; } /* Translate the symbols' shndx fields from a part index to a section * header index. */ static void complete(elfpart *part, blueprint const *bp) { Elf32_Sym *sym; int i, n; for (i = 0, sym = part->part ; i < part->count ; ++i, ++sym) { if (sym->st_shndx > 0 && sym->st_shndx < bp->partcount) { n = sym->st_shndx; sym->st_shndx = 1;