/* * If CREATE/SET, add new options to array; if RESET, just check that the * user didn't say RESET (option=val). (Must do this because the grammar * doesn't enforce it.) */ foreach(cell, defList) { DefElem *def = (DefElem *) lfirst(cell); if (isReset) { if (def->arg != NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("RESET must not include values for parameters"))); } else { text *t; const char *value; Size len; /* * Error out if the namespace is not valid. A NULL namespace is * always valid. */ if (def->defnamespace != NULL) { bool valid = false; int i; if (validnsps) { for (i = 0; validnsps[i]; i++) { if (pg_strcasecmp(def->defnamespace, validnsps[i]) == 0) { valid = true; break; } } } if (!valid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized parameter namespace \"%s\"", def->defnamespace))); } if (ignoreOids && pg_strcasecmp(def->defname, "oids") == 0) continue; /* ignore if not in the same namespace */ if (namspace == NULL) { if (def->defnamespace != NULL) continue; } else if (def->defnamespace == NULL) continue; else if (pg_strcasecmp(def->defnamespace, namspace) != 0) continue; /* * Flatten the DefElem into a text string like "name=arg". If we * have just "name", assume "name=true" is meant. Note: the * namespace is not output. */ if (def->arg != NULL) value = defGetString(def); else value = "true"; len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", def->defname, value); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } }
/* * Transform a relation options list (list of DefElem) into the text array * format that is kept in pg_class.reloptions, including only those options * that are in the passed namespace. The output values do not include the * namespace. * * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and * ALTER TABLE RESET. In the ALTER cases, oldOptions is the existing * reloptions value (possibly NULL), and we replace or remove entries * as needed. * * If ignoreOids is true, then we should ignore any occurrence of "oids" * in the list (it will be or has been handled by interpretOidsOption()). * * Note that this is not responsible for determining whether the options * are valid, but it does check that namespaces for all the options given are * listed in validnsps. The NULL namespace is always valid and need not be * explicitly listed. Passing a NULL pointer means that only the NULL * namespace is valid. * * Both oldOptions and the result are text arrays (or NULL for "default"), * but we declare them as Datums to avoid including array.h in reloptions.h. */ Datum transformRelOptions(Datum oldOptions, List *defList, char *namspace, char *validnsps[], bool ignoreOids, bool isReset) { Datum result; ArrayBuildState *astate; ListCell *cell; /* no change if empty list */ if (defList == NIL) return oldOptions; /* We build new array using accumArrayResult */ astate = NULL; /* Copy any oldOptions that aren't to be replaced */ if (PointerIsValid(DatumGetPointer(oldOptions))) { ArrayType *array = DatumGetArrayTypeP(oldOptions); Datum *oldoptions; int noldoptions; int i; deconstruct_array(array, TEXTOID, -1, false, 'i', &oldoptions, NULL, &noldoptions); for (i = 0; i < noldoptions; i++) { text *oldoption = DatumGetTextP(oldoptions[i]); char *text_str = VARDATA(oldoption); int text_len = VARSIZE(oldoption) - VARHDRSZ; /* Search for a match in defList */ foreach(cell, defList) { DefElem *def = (DefElem *) lfirst(cell); int kw_len; /* ignore if not in the same namespace */ if (namspace == NULL) { if (def->defnamespace != NULL) continue; } else if (def->defnamespace == NULL) continue; else if (pg_strcasecmp(def->defnamespace, namspace) != 0) continue; kw_len = strlen(def->defname); if (text_len > kw_len && text_str[kw_len] == '=' && pg_strncasecmp(text_str, def->defname, kw_len) == 0) break; } if (!cell) { /* No match, so keep old option */ astate = accumArrayResult(astate, oldoptions[i], false, TEXTOID, CurrentMemoryContext); } } }
/*----------------------------------------------------------------------------- * 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)); }
/* * tuple_data_split_internal * * Split raw tuple data taken directly from a page into an array of bytea * elements. This routine does a lookup on NULL values and creates array * elements accordingly. This is a reimplementation of nocachegetattr() * in heaptuple.c simplified for educational purposes. */ static Datum tuple_data_split_internal(Oid relid, char *tupdata, uint16 tupdata_len, uint16 t_infomask, uint16 t_infomask2, bits8 *t_bits, bool do_detoast) { ArrayBuildState *raw_attrs; int nattrs; int i; int off = 0; Relation rel; TupleDesc tupdesc; /* Get tuple descriptor from relation OID */ rel = relation_open(relid, NoLock); tupdesc = CreateTupleDescCopyConstr(rel->rd_att); relation_close(rel, NoLock); raw_attrs = initArrayResult(BYTEAOID, CurrentMemoryContext, false); nattrs = tupdesc->natts; if (nattrs < (t_infomask2 & HEAP_NATTS_MASK)) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("number of attributes in tuple header is greater than number of attributes in tuple descriptor"))); for (i = 0; i < nattrs; i++) { Form_pg_attribute attr; bool is_null; bytea *attr_data = NULL; attr = tupdesc->attrs[i]; is_null = (t_infomask & HEAP_HASNULL) && att_isnull(i, t_bits); /* * Tuple header can specify less attributes than tuple descriptor as * ALTER TABLE ADD COLUMN without DEFAULT keyword does not actually * change tuples in pages, so attributes with numbers greater than * (t_infomask2 & HEAP_NATTS_MASK) should be treated as NULL. */ if (i >= (t_infomask2 & HEAP_NATTS_MASK)) is_null = true; if (!is_null) { int len; if (attr->attlen == -1) { off = att_align_pointer(off, tupdesc->attrs[i]->attalign, -1, tupdata + off); /* * As VARSIZE_ANY throws an exception if it can't properly * detect the type of external storage in macros VARTAG_SIZE, * this check is repeated to have a nicer error handling. */ if (VARATT_IS_EXTERNAL(tupdata + off) && !VARATT_IS_EXTERNAL_ONDISK(tupdata + off) && !VARATT_IS_EXTERNAL_INDIRECT(tupdata + off)) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("first byte of varlena attribute is incorrect for attribute %d", i))); len = VARSIZE_ANY(tupdata + off); } else { off = att_align_nominal(off, tupdesc->attrs[i]->attalign); len = attr->attlen; } if (tupdata_len < off + len) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("unexpected end of tuple data"))); if (attr->attlen == -1 && do_detoast) attr_data = DatumGetByteaPCopy(tupdata + off); else { attr_data = (bytea *) palloc(len + VARHDRSZ); SET_VARSIZE(attr_data, len + VARHDRSZ); memcpy(VARDATA(attr_data), tupdata + off, len); } off = att_addlength_pointer(off, tupdesc->attrs[i]->attlen, tupdata + off); } raw_attrs = accumArrayResult(raw_attrs, PointerGetDatum(attr_data), is_null, BYTEAOID, CurrentMemoryContext); if (attr_data) pfree(attr_data); } if (tupdata_len != off) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("end of tuple reached without looking at all its data"))); return makeArrayResult(raw_attrs, CurrentMemoryContext); }
/* * Accumulate a new datum for one AO storage option. */ static void accumAOStorageOpt(char *name, char *value, ArrayBuildState *astate, bool *foundAO, bool *aovalue) { text *t; bool boolval; int intval; StringInfoData buf; Assert(astate); initStringInfo(&buf); if (pg_strcasecmp(SOPT_APPENDONLY, name) == 0) { if (!parse_bool(value, &boolval)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid bool value \"%s\" for storage option \"%s\"", value, name))); /* "appendonly" option is explicitly specified. */ if (foundAO != NULL) *foundAO = true; if (aovalue != NULL) *aovalue = boolval; /* * Record value of "appendonly" option as true always. Return * the value specified by user in aovalue. Setting * appendonly=true always in the array of datums enables us to * reuse default_reloptions() and * validateAppendOnlyRelOptions(). If validations are * successful, we keep the user specified value for * appendonly. */ appendStringInfo(&buf, "%s=%s", SOPT_APPENDONLY, "true"); } else if (pg_strcasecmp(SOPT_BLOCKSIZE, name) == 0) { if (!parse_int(value, &intval, 0 /* unit flags */, NULL /* hint message */)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid integer value \"%s\" for storage option \"%s\"", value, name))); appendStringInfo(&buf, "%s=%d", SOPT_BLOCKSIZE, intval); } else if (pg_strcasecmp(SOPT_COMPTYPE, name) == 0) { appendStringInfo(&buf, "%s=%s", SOPT_COMPTYPE, value); } else if (pg_strcasecmp(SOPT_COMPLEVEL, name) == 0) { if (!parse_int(value, &intval, 0 /* unit flags */, NULL /* hint message */)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid integer value \"%s\" for storage option \"%s\"", value, name))); appendStringInfo(&buf, "%s=%d", SOPT_COMPLEVEL, intval); } else if (pg_strcasecmp(SOPT_CHECKSUM, name) == 0) { if (!parse_bool(value, &boolval)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid bool value \"%s\" for storage option \"%s\"", value, name))); appendStringInfo(&buf, "%s=%s", SOPT_CHECKSUM, boolval ? "true" : "false"); } else if (pg_strcasecmp(SOPT_ORIENTATION, name) == 0) { if ((pg_strcasecmp(value, "row") != 0) && (pg_strcasecmp(value, "column") != 0)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid value \"%s\" for storage option \"%s\"", value, name))); } appendStringInfo(&buf, "%s=%s", SOPT_ORIENTATION, value); } else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid storage option \"%s\"", name))); } t = cstring_to_text(buf.data); accumArrayResult(astate, PointerGetDatum(t), /* disnull */ false, TEXTOID, CurrentMemoryContext); pfree(t); pfree(buf.data); }
/* * Return a datum that is array of "name=value" strings for each * appendonly storage option in opts. This datum is used to populate * pg_class.reloptions during relation creation. * * To avoid catalog bloat, we only create "name=value" item for those * values in opts that are not specified in WITH clause and are * different from their initial defaults. */ Datum transformAOStdRdOptions(StdRdOptions *opts, Datum withOpts) { char *strval; char intval[MAX_SOPT_VALUE_LEN]; Datum *withDatums = NULL; text *t; int len, i, withLen, soptLen, nWithOpts = 0; ArrayType *withArr; ArrayBuildState *astate = NULL; bool foundAO = false, foundBlksz = false, foundComptype = false, foundComplevel = false, foundChecksum = false, foundOrientation = false; /* * withOpts must be parsed to see if an option was spcified in WITH() * clause. */ if (DatumGetPointer(withOpts) != NULL) { withArr = DatumGetArrayTypeP(withOpts); Assert(ARR_ELEMTYPE(withArr) == TEXTOID); deconstruct_array(withArr, TEXTOID, -1, false, 'i', &withDatums, NULL, &nWithOpts); /* * Include options specified in WITH() clause in the same order as * they are specified. Otherwise we will end up with regression * failures due to diff with respect to answer file. */ for (i = 0; i < nWithOpts; ++i) { t = DatumGetTextP(withDatums[i]); strval = VARDATA(t); /* * Text datums are usually not null terminated. We must never * access beyond their length. */ withLen = VARSIZE(t) - VARHDRSZ; /* * withDatums[i] may not be used directly. It may be e.g. * "aPPendOnly=tRue". Therefore we don't set it as reloptions as * is. */ soptLen = strlen(SOPT_APPENDONLY); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_APPENDONLY, soptLen) == 0) { foundAO = true; strval = opts->appendonly ? "true" : "false"; len = VARHDRSZ + strlen(SOPT_APPENDONLY) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_APPENDONLY, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } soptLen = strlen(SOPT_BLOCKSIZE); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_BLOCKSIZE, soptLen) == 0) { foundBlksz = true; snprintf(intval, MAX_SOPT_VALUE_LEN, "%d", opts->blocksize); len = VARHDRSZ + strlen(SOPT_BLOCKSIZE) + 1 + strlen(intval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_BLOCKSIZE, intval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } soptLen = strlen(SOPT_COMPTYPE); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_COMPTYPE, soptLen) == 0) { foundComptype = true; /* * Record "none" as compresstype in reloptions if it was * explicitly specified in WITH clause. */ strval = (opts->compresstype != NULL) ? opts->compresstype : "none"; len = VARHDRSZ + strlen(SOPT_COMPTYPE) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_COMPTYPE, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } soptLen = strlen(SOPT_COMPLEVEL); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_COMPLEVEL, soptLen) == 0) { foundComplevel = true; snprintf(intval, MAX_SOPT_VALUE_LEN, "%d", opts->compresslevel); len = VARHDRSZ + strlen(SOPT_COMPLEVEL) + 1 + strlen(intval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_COMPLEVEL, intval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } soptLen = strlen(SOPT_CHECKSUM); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_CHECKSUM, soptLen) == 0) { foundChecksum = true; strval = opts->checksum ? "true" : "false"; len = VARHDRSZ + strlen(SOPT_CHECKSUM) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_CHECKSUM, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } soptLen = strlen(SOPT_ORIENTATION); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_ORIENTATION, soptLen) == 0) { foundOrientation = true; strval = opts->columnstore ? "column" : "row"; len = VARHDRSZ + strlen(SOPT_ORIENTATION) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_ORIENTATION, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } /* * Record fillfactor only if it's specified in WITH clause. * Default fillfactor is assumed otherwise. */ soptLen = strlen(SOPT_FILLFACTOR); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_FILLFACTOR, soptLen) == 0) { snprintf(intval, MAX_SOPT_VALUE_LEN, "%d", opts->fillfactor); len = VARHDRSZ + strlen(SOPT_FILLFACTOR) + 1 + strlen(intval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_FILLFACTOR, intval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } } } /* Include options that are not defaults and not already included. */ if ((opts->appendonly != AO_DEFAULT_APPENDONLY) && !foundAO) { /* appendonly */ strval = opts->appendonly ? "true" : "false"; len = VARHDRSZ + strlen(SOPT_APPENDONLY) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_APPENDONLY, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } if ((opts->blocksize != AO_DEFAULT_BLOCKSIZE) && !foundBlksz) { /* blocksize */ snprintf(intval, MAX_SOPT_VALUE_LEN, "%d", opts->blocksize); len = VARHDRSZ + strlen(SOPT_BLOCKSIZE) + 1 + strlen(intval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_BLOCKSIZE, intval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } /* * Record compression options only if compression is enabled. No need to * check compresstype here as by the time we get here, "opts" should have * been set by default_reloptions() correctly. */ if (opts->compresslevel > AO_DEFAULT_COMPRESSLEVEL && opts->compresstype != NULL) { if (!foundComptype && ( (pg_strcasecmp(opts->compresstype, AO_DEFAULT_COMPRESSTYPE) == 0 && opts->compresslevel == 1 && !foundComplevel) || pg_strcasecmp(opts->compresstype, AO_DEFAULT_COMPRESSTYPE) != 0)) { /* compress type */ strval = opts->compresstype; len = VARHDRSZ + strlen(SOPT_COMPTYPE) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_COMPTYPE, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } /* When compression is enabled, default compresslevel is 1. */ if ((opts->compresslevel != 1) && !foundComplevel) { /* compress level */ snprintf(intval, MAX_SOPT_VALUE_LEN, "%d", opts->compresslevel); len = VARHDRSZ + strlen(SOPT_COMPLEVEL) + 1 + strlen(intval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_COMPLEVEL, intval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } } if ((opts->checksum != AO_DEFAULT_CHECKSUM) && !foundChecksum) { /* checksum */ strval = opts->checksum ? "true" : "false"; len = VARHDRSZ + strlen(SOPT_CHECKSUM) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_CHECKSUM, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } if ((opts->columnstore != AO_DEFAULT_COLUMNSTORE) && !foundOrientation) { /* orientation */ strval = opts->columnstore ? "column" : "row"; len = VARHDRSZ + strlen(SOPT_ORIENTATION) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_ORIENTATION, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } return astate ? makeArrayResult(astate, CurrentMemoryContext) : PointerGetDatum(NULL); }