/* * master_drop_sequences attempts to drop a list of sequences on worker nodes. * The "IF EXISTS" clause is used to permit dropping sequences even if they may not * exist. If the commands fail on the workers, the operation is rolled back. * If ddl propagation (citus.enable_ddl_propagation) is set to off, then the function * returns without doing anything. */ Datum master_drop_sequences(PG_FUNCTION_ARGS) { ArrayType *sequenceNamesArray = PG_GETARG_ARRAYTYPE_P(0); ArrayIterator sequenceIterator = NULL; Datum sequenceText = 0; bool isNull = false; StringInfo dropSeqCommand = makeStringInfo(); bool coordinator = IsCoordinator(); CheckCitusVersion(ERROR); /* do nothing if DDL propagation is switched off or this is not the coordinator */ if (!EnableDDLPropagation || !coordinator) { PG_RETURN_VOID(); } /* iterate over sequence names to build single command to DROP them all */ sequenceIterator = array_create_iterator(sequenceNamesArray, 0, NULL); while (array_iterate(sequenceIterator, &sequenceText, &isNull)) { if (isNull) { ereport(ERROR, (errmsg("unexpected NULL sequence name"), errcode(ERRCODE_INVALID_PARAMETER_VALUE))); } /* append command portion if we haven't added any sequence names yet */ if (dropSeqCommand->len == 0) { appendStringInfoString(dropSeqCommand, "DROP SEQUENCE IF EXISTS"); } else { /* otherwise, add a comma to separate subsequent sequence names */ appendStringInfoChar(dropSeqCommand, ','); } appendStringInfo(dropSeqCommand, " %s", TextDatumGetCString(sequenceText)); } if (dropSeqCommand->len != 0) { appendStringInfoString(dropSeqCommand, " CASCADE"); SendCommandToWorkers(WORKERS_WITH_METADATA, DISABLE_DDL_PROPAGATION); SendCommandToWorkers(WORKERS_WITH_METADATA, dropSeqCommand->data); } PG_RETURN_VOID(); }
/* * CmsTopnUnion is a helper function for union operations. It first sums up two * sketchs and iterates through the top-n array of the second cms_topn to update * the top-n of union. */ static CmsTopn * CmsTopnUnion(CmsTopn *firstCmsTopn, CmsTopn *secondCmsTopn, TypeCacheEntry *itemTypeCacheEntry) { ArrayType *firstTopnArray = TopnArray(firstCmsTopn); ArrayType *secondTopnArray = TopnArray(secondCmsTopn); ArrayType *newTopnArray = NULL; Size firstTopnArrayLength = ARR_DIMS(firstTopnArray)[0]; Size secondTopnArrayLength = ARR_DIMS(secondTopnArray)[0]; Size sketchSize = 0; CmsTopn *newCmsTopn = NULL; uint32 topnItemIndex = 0; Datum topnItem = 0; ArrayIterator secondTopnArrayIterator = NULL; bool isNull = false; bool hasMoreItem = false; if (firstCmsTopn->sketchDepth != secondCmsTopn->sketchDepth || firstCmsTopn->sketchWidth != secondCmsTopn->sketchWidth || firstCmsTopn->topnItemCount != secondCmsTopn->topnItemCount) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot merge cms_topns with different parameters"))); } if (firstTopnArrayLength == 0) { newCmsTopn = secondCmsTopn; } else if (secondTopnArrayLength == 0) { newCmsTopn = firstCmsTopn; } else if (ARR_ELEMTYPE(firstTopnArray) != ARR_ELEMTYPE(secondTopnArray)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot merge cms_topns of different types"))); } else { /* for performance reasons we use first cms_topn to merge two cms_topn's */ newCmsTopn = firstCmsTopn; newTopnArray = firstTopnArray; sketchSize = newCmsTopn->sketchDepth * newCmsTopn->sketchWidth; for (topnItemIndex = 0; topnItemIndex < sketchSize; topnItemIndex++) { newCmsTopn->sketch[topnItemIndex] += secondCmsTopn->sketch[topnItemIndex]; } secondTopnArrayIterator = array_create_iterator(secondTopnArray, 0); /* * One by one we add top-n items in second top-n array to top-n array of * first(new) top-n array. As a result only top-n elements of two item * list are kept. */ hasMoreItem = array_iterate(secondTopnArrayIterator, &topnItem, &isNull); while (hasMoreItem) { Frequency newItemFrequency = 0; bool topnArrayUpdated = false; newItemFrequency = CmsTopnEstimateItemFrequency(newCmsTopn, topnItem, itemTypeCacheEntry); newTopnArray = UpdateTopnArray(newCmsTopn, topnItem, itemTypeCacheEntry, newItemFrequency, &topnArrayUpdated); if (topnArrayUpdated) { newCmsTopn = FormCmsTopn(newCmsTopn, newTopnArray); } hasMoreItem = array_iterate(secondTopnArrayIterator, &topnItem, &isNull); } } return newCmsTopn; }
/* * UpdateTopnArray is a helper function for the unions and inserts. It takes * given item and its frequency. If the item is not in the top-n array, it tries * to insert new item. If there is place in the top-n array, it insert directly. * Otherwise, it compares its frequency with the minimum of current items in the * array and updates top-n array if new frequency is bigger. */ static ArrayType * UpdateTopnArray(CmsTopn *cmsTopn, Datum candidateItem, TypeCacheEntry *itemTypeCacheEntry, Frequency itemFrequency, bool *topnArrayUpdated) { ArrayType *currentTopnArray = TopnArray(cmsTopn); ArrayType *updatedTopnArray = NULL; int16 itemTypeLength = itemTypeCacheEntry->typlen; bool itemTypeByValue = itemTypeCacheEntry->typbyval; char itemTypeAlignment = itemTypeCacheEntry->typalign; Frequency minOfNewTopnItems = MAX_FREQUENCY; bool candidateAlreadyInArray = false; int candidateIndex = -1; int currentArrayLength = ARR_DIMS(currentTopnArray)[0]; if (currentArrayLength == 0) { Oid itemType = itemTypeCacheEntry->type_id; if (itemTypeCacheEntry->typtype == TYPTYPE_COMPOSITE) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("composite types are not supported"))); } currentTopnArray = construct_empty_array(itemType); } /* * If item frequency is smaller than min frequency of old top-n items, * it cannot be in the top-n items and we insert it only if there is place. * Otherwise, we need to check new minimum and whether it is in the top-n. */ if (itemFrequency <= cmsTopn->minFrequencyOfTopnItems) { if (currentArrayLength < cmsTopn->topnItemCount) { candidateIndex = currentArrayLength + 1; minOfNewTopnItems = itemFrequency; } } else { ArrayIterator iterator = array_create_iterator(currentTopnArray, 0); Datum topnItem = 0; int topnItemIndex = 1; int minItemIndex = 1; bool hasMoreItem = false; bool isNull = false; /* * Find the top-n item with minimum frequency to replace it with candidate * item. */ hasMoreItem = array_iterate(iterator, &topnItem, &isNull); while (hasMoreItem) { Frequency topnItemFrequency = 0; /* check if we already have this item in top-n array */ candidateAlreadyInArray = datumIsEqual(topnItem, candidateItem, itemTypeByValue, itemTypeLength); if (candidateAlreadyInArray) { minItemIndex = -1; break; } /* keep track of minumum frequency item in the top-n array */ topnItemFrequency = CmsTopnEstimateItemFrequency(cmsTopn, topnItem, itemTypeCacheEntry); if (topnItemFrequency < minOfNewTopnItems) { minOfNewTopnItems = topnItemFrequency; minItemIndex = topnItemIndex; } hasMoreItem = array_iterate(iterator, &topnItem, &isNull); topnItemIndex++; } /* if new item is not in the top-n and there is place, insert the item */ if (!candidateAlreadyInArray && currentArrayLength < cmsTopn->topnItemCount) { minItemIndex = currentArrayLength + 1; minOfNewTopnItems = Min(minOfNewTopnItems, itemFrequency); } candidateIndex = minItemIndex; } /* * If it is not in the top-n structure and its frequency bigger than minimum * put into top-n instead of the item which has minimum frequency. If it is * in top-n or not frequent items, do not change anything. */ if (!candidateAlreadyInArray && minOfNewTopnItems <= itemFrequency) { updatedTopnArray = array_set(currentTopnArray, 1, &candidateIndex, candidateItem, false, -1, itemTypeLength, itemTypeByValue, itemTypeAlignment); cmsTopn->minFrequencyOfTopnItems = minOfNewTopnItems; *topnArrayUpdated = true; } else { updatedTopnArray = currentTopnArray; *topnArrayUpdated = false; } return updatedTopnArray; }
/* * topn is a user-facing UDF which returns the top items and their frequencies. * It first gets the top-n structure and converts it into the ordered array of * FrequentTopnItem which keeps Datums and the frequencies in the first call. * Then, it returns an item and its frequency according to call counter. This * function requires a parameter for the type because PostgreSQL has strongly * typed system and the type of frequent items in returning rows has to be given. */ Datum topn(PG_FUNCTION_ARGS) { FuncCallContext *functionCallContext = NULL; TupleDesc tupleDescriptor = NULL; Oid returningItemType = get_fn_expr_argtype(fcinfo->flinfo, 1); CmsTopn *cmsTopn = NULL; ArrayType *topnArray = NULL; int topnArrayLength = 0; Datum topnItem = 0; bool isNull = false; ArrayIterator topnIterator = NULL; bool hasMoreItem = false; int callCounter = 0; int maxCallCounter = 0; if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext = NULL; Size topnArraySize = 0; TypeCacheEntry *itemTypeCacheEntry = NULL; int topnIndex = 0; Oid itemType = InvalidOid; FrequentTopnItem *sortedTopnArray = NULL; TupleDesc completeTupleDescriptor = NULL; functionCallContext = SRF_FIRSTCALL_INIT(); if (PG_ARGISNULL(0)) { SRF_RETURN_DONE(functionCallContext); } cmsTopn = (CmsTopn *) PG_GETARG_VARLENA_P(0); topnArray = TopnArray(cmsTopn); topnArrayLength = ARR_DIMS(topnArray)[0]; /* if there is not any element in the array just return */ if (topnArrayLength == 0) { SRF_RETURN_DONE(functionCallContext); } itemType = ARR_ELEMTYPE(topnArray); if (itemType != returningItemType) { elog(ERROR, "not a proper cms_topn for the result type"); } /* switch to consistent context for multiple calls */ oldcontext = MemoryContextSwitchTo(functionCallContext->multi_call_memory_ctx); itemTypeCacheEntry = lookup_type_cache(itemType, 0); functionCallContext->max_calls = topnArrayLength; /* create an array to copy top-n items and sort them later */ topnArraySize = sizeof(FrequentTopnItem) * topnArrayLength; sortedTopnArray = palloc0(topnArraySize); topnIterator = array_create_iterator(topnArray, 0); hasMoreItem = array_iterate(topnIterator, &topnItem, &isNull); while (hasMoreItem) { FrequentTopnItem frequentTopnItem; frequentTopnItem.topnItem = topnItem; frequentTopnItem.topnItemFrequency = CmsTopnEstimateItemFrequency(cmsTopn, topnItem, itemTypeCacheEntry); sortedTopnArray[topnIndex] = frequentTopnItem; hasMoreItem = array_iterate(topnIterator, &topnItem, &isNull); topnIndex++; } SortTopnItems(sortedTopnArray, topnArrayLength); functionCallContext->user_fctx = sortedTopnArray; get_call_result_type(fcinfo, &returningItemType, &tupleDescriptor); completeTupleDescriptor = BlessTupleDesc(tupleDescriptor); functionCallContext->tuple_desc = completeTupleDescriptor; MemoryContextSwitchTo(oldcontext); } functionCallContext = SRF_PERCALL_SETUP(); maxCallCounter = functionCallContext->max_calls; callCounter = functionCallContext->call_cntr; if (callCounter < maxCallCounter) { Datum *tupleValues = (Datum *) palloc(2 * sizeof(Datum)); HeapTuple topnItemTuple; Datum topnItemDatum = 0; char *tupleNulls = (char *) palloc0(2 * sizeof(char)); FrequentTopnItem *sortedTopnArray = NULL; TupleDesc completeTupleDescriptor = NULL; sortedTopnArray = (FrequentTopnItem *) functionCallContext->user_fctx; tupleValues[0] = sortedTopnArray[callCounter].topnItem; tupleValues[1] = sortedTopnArray[callCounter].topnItemFrequency; /* non-null attributes are indicated by a ' ' (space) */ tupleNulls[0] = ' '; tupleNulls[1] = ' '; completeTupleDescriptor = functionCallContext->tuple_desc; topnItemTuple = heap_formtuple(completeTupleDescriptor, tupleValues, tupleNulls); topnItemDatum = HeapTupleGetDatum(topnItemTuple); SRF_RETURN_NEXT(functionCallContext, topnItemDatum); } else { SRF_RETURN_DONE(functionCallContext); } }
/*----------------------------------------------------------------------------- * array_positions : * return an array of positions of a value in an array. * * IS NOT DISTINCT FROM semantics are used for comparisons. Returns NULL when * the input array is NULL. When the value is not found in the array, returns * an empty array. * * This is not strict so we have to test for null inputs explicitly. *----------------------------------------------------------------------------- */ Datum array_positions(PG_FUNCTION_ARGS) { ArrayType *array; Oid collation = PG_GET_COLLATION(); Oid element_type; Datum searched_element, value; bool isnull; int position; TypeCacheEntry *typentry; ArrayMetaState *my_extra; bool null_search; ArrayIterator array_iterator; ArrayBuildState *astate = NULL; if (PG_ARGISNULL(0)) PG_RETURN_NULL(); array = PG_GETARG_ARRAYTYPE_P(0); element_type = ARR_ELEMTYPE(array); position = (ARR_LBOUND(array))[0] - 1; /* * We refuse to search for elements in multi-dimensional arrays, since we * have no good way to report the element's location in the array. */ if (ARR_NDIM(array) > 1) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("searching for elements in multidimensional arrays is not supported"))); astate = initArrayResult(INT4OID, CurrentMemoryContext, false); if (PG_ARGISNULL(1)) { /* fast return when the array doesn't have nulls */ if (!array_contains_nulls(array)) PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); searched_element = (Datum) 0; null_search = true; } else { searched_element = PG_GETARG_DATUM(1); null_search = false; } /* * We arrange to look up type info for array_create_iterator only once per * series of calls, assuming the element type doesn't change underneath * us. */ my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; if (my_extra == NULL) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(ArrayMetaState)); my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; my_extra->element_type = ~element_type; } if (my_extra->element_type != element_type) { get_typlenbyvalalign(element_type, &my_extra->typlen, &my_extra->typbyval, &my_extra->typalign); typentry = lookup_type_cache(element_type, TYPECACHE_EQ_OPR_FINFO); if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("could not identify an equality operator for type %s", format_type_be(element_type)))); my_extra->element_type = element_type; fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &my_extra->proc, fcinfo->flinfo->fn_mcxt); } /* * Accumulate each array position iff the element matches the given * element. */ array_iterator = array_create_iterator(array, 0, my_extra); while (array_iterate(array_iterator, &value, &isnull)) { position += 1; /* * Can't look at the array element's value if it's null; but if we * search for null, we have a hit. */ if (isnull || null_search) { if (isnull && null_search) astate = accumArrayResult(astate, Int32GetDatum(position), false, INT4OID, CurrentMemoryContext); continue; } /* not nulls, so run the operator */ if (DatumGetBool(FunctionCall2Coll(&my_extra->proc, collation, searched_element, value))) astate = accumArrayResult(astate, Int32GetDatum(position), false, INT4OID, CurrentMemoryContext); } array_free_iterator(array_iterator); /* Avoid leaking memory when handed toasted input */ PG_FREE_IF_COPY(array, 0); PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); }
/* * array_position_common * Common code for array_position and array_position_start * * These are separate wrappers for the sake of opr_sanity regression test. * They are not strict so we have to test for null inputs explicitly. */ static Datum array_position_common(FunctionCallInfo fcinfo) { ArrayType *array; Oid collation = PG_GET_COLLATION(); Oid element_type; Datum searched_element, value; bool isnull; int position, position_min; bool found = false; TypeCacheEntry *typentry; ArrayMetaState *my_extra; bool null_search; ArrayIterator array_iterator; if (PG_ARGISNULL(0)) PG_RETURN_NULL(); array = PG_GETARG_ARRAYTYPE_P(0); element_type = ARR_ELEMTYPE(array); /* * We refuse to search for elements in multi-dimensional arrays, since we * have no good way to report the element's location in the array. */ if (ARR_NDIM(array) > 1) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("searching for elements in multidimensional arrays is not supported"))); if (PG_ARGISNULL(1)) { /* fast return when the array doesn't have nulls */ if (!array_contains_nulls(array)) PG_RETURN_NULL(); searched_element = (Datum) 0; null_search = true; } else { searched_element = PG_GETARG_DATUM(1); null_search = false; } position = (ARR_LBOUND(array))[0] - 1; /* figure out where to start */ if (PG_NARGS() == 3) { if (PG_ARGISNULL(2)) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("initial position must not be null"))); position_min = PG_GETARG_INT32(2); } else position_min = (ARR_LBOUND(array))[0]; /* * We arrange to look up type info for array_create_iterator only once per * series of calls, assuming the element type doesn't change underneath * us. */ my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; if (my_extra == NULL) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(ArrayMetaState)); my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; my_extra->element_type = ~element_type; } if (my_extra->element_type != element_type) { get_typlenbyvalalign(element_type, &my_extra->typlen, &my_extra->typbyval, &my_extra->typalign); typentry = lookup_type_cache(element_type, TYPECACHE_EQ_OPR_FINFO); if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("could not identify an equality operator for type %s", format_type_be(element_type)))); my_extra->element_type = element_type; fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &my_extra->proc, fcinfo->flinfo->fn_mcxt); } /* Examine each array element until we find a match. */ array_iterator = array_create_iterator(array, 0, my_extra); while (array_iterate(array_iterator, &value, &isnull)) { position++; /* skip initial elements if caller requested so */ if (position < position_min) continue; /* * Can't look at the array element's value if it's null; but if we * search for null, we have a hit and are done. */ if (isnull || null_search) { if (isnull && null_search) { found = true; break; } else continue; } /* not nulls, so run the operator */ if (DatumGetBool(FunctionCall2Coll(&my_extra->proc, collation, searched_element, value))) { found = true; break; } } array_free_iterator(array_iterator); /* Avoid leaking memory when handed toasted input */ PG_FREE_IF_COPY(array, 0); if (!found) PG_RETURN_NULL(); PG_RETURN_INT32(position); }