/* ---------- * toast_flatten_tuple_attribute - * * If a Datum is of composite type, "flatten" it to contain no toasted fields. * This must be invoked on any potentially-composite field that is to be * inserted into a tuple. Doing this preserves the invariant that toasting * goes only one level deep in a tuple. * * Note that flattening does not mean expansion of short-header varlenas, * so in one sense toasting is allowed within composite datums. * ---------- */ Datum toast_flatten_tuple_attribute(Datum value, Oid typeId, int32 typeMod) { TupleDesc tupleDesc; HeapTupleHeader olddata; HeapTupleHeader new_data; int32 new_len; int32 new_data_len; HeapTupleData tmptup; Form_pg_attribute *att; int numAttrs; int i; bool need_change = false; bool has_nulls = false; Datum toast_values[MaxTupleAttributeNumber]; bool toast_isnull[MaxTupleAttributeNumber]; bool toast_free[MaxTupleAttributeNumber]; /* * See if it's a composite type, and get the tupdesc if so. */ tupleDesc = lookup_rowtype_tupdesc_noerror(typeId, typeMod, true); if (tupleDesc == NULL) return value; /* not a composite type */ att = tupleDesc->attrs; numAttrs = tupleDesc->natts; /* * Break down the tuple into fields. */ olddata = DatumGetHeapTupleHeader(value); Assert(typeId == HeapTupleHeaderGetTypeId(olddata)); Assert(typeMod == HeapTupleHeaderGetTypMod(olddata)); /* Build a temporary HeapTuple control structure */ tmptup.t_len = HeapTupleHeaderGetDatumLength(olddata); ItemPointerSetInvalid(&(tmptup.t_self)); tmptup.t_tableOid = InvalidOid; #ifdef PGXC tmptup.t_xc_node_id = 0; #endif tmptup.t_data = olddata; Assert(numAttrs <= MaxTupleAttributeNumber); heap_deform_tuple(&tmptup, tupleDesc, toast_values, toast_isnull); memset(toast_free, 0, numAttrs * sizeof(bool)); for (i = 0; i < numAttrs; i++) { /* * Look at non-null varlena attributes */ if (toast_isnull[i]) has_nulls = true; else if (att[i]->attlen == -1) { struct varlena *new_value; new_value = (struct varlena *) DatumGetPointer(toast_values[i]); if (VARATT_IS_EXTERNAL(new_value) || VARATT_IS_COMPRESSED(new_value)) { new_value = heap_tuple_untoast_attr(new_value); toast_values[i] = PointerGetDatum(new_value); toast_free[i] = true; need_change = true; } } } /* * If nothing to untoast, just return the original tuple. */ if (!need_change) { ReleaseTupleDesc(tupleDesc); return value; } /* * Calculate the new size of the tuple. Header size should not change, * but data size might. */ new_len = offsetof(HeapTupleHeaderData, t_bits); if (has_nulls) new_len += BITMAPLEN(numAttrs); if (olddata->t_infomask & HEAP_HASOID) new_len += sizeof(Oid); new_len = MAXALIGN(new_len); Assert(new_len == olddata->t_hoff); new_data_len = heap_compute_data_size(tupleDesc, toast_values, toast_isnull); new_len += new_data_len; new_data = (HeapTupleHeader) palloc0(new_len); /* * Put the tuple header and the changed values into place */ memcpy(new_data, olddata, olddata->t_hoff); HeapTupleHeaderSetDatumLength(new_data, new_len); heap_fill_tuple(tupleDesc, toast_values, toast_isnull, (char *) new_data + olddata->t_hoff, new_data_len, &(new_data->t_infomask), has_nulls ? new_data->t_bits : NULL); /* * Free allocated temp values */ for (i = 0; i < numAttrs; i++) if (toast_free[i]) pfree(DatumGetPointer(toast_values[i])); ReleaseTupleDesc(tupleDesc); return PointerGetDatum(new_data); }
/* * record_out - output routine for any composite type. */ Datum record_out(PG_FUNCTION_ARGS) { HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tuple; RecordIOData *my_extra; bool needComma = false; int ncolumns; int i; Datum *values; bool *nulls; StringInfoData buf; /* Extract type info from the tuple itself */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_data = rec; /* * We arrange to look up the needed I/O info just once per series of * calls, assuming the record type doesn't change underneath us. */ my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns != ncolumns) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || my_extra->record_typmod != tupTypmod) { MemSet(my_extra, 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra->record_type = tupType; my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); /* Break down the tuple into fields */ heap_deform_tuple(&tuple, tupdesc, values, nulls); /* And build the result string */ initStringInfo(&buf); appendStringInfoChar(&buf, '('); for (i = 0; i < ncolumns; i++) { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; char *value; char *tmp; bool nq; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) continue; if (needComma) appendStringInfoChar(&buf, ','); needComma = true; if (nulls[i]) { /* emit nothing... */ continue; } /* * Convert the column value to text */ if (column_info->column_type != column_type) { bool typIsVarlena; getTypeOutputInfo(column_type, &column_info->typiofunc, &typIsVarlena); fmgr_info_cxt(column_info->typiofunc, &column_info->proc, fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } value = OutputFunctionCall(&column_info->proc, values[i]); /* Detect whether we need double quotes for this value */ nq = (value[0] == '\0'); /* force quotes for empty string */ for (tmp = value; *tmp; tmp++) { char ch = *tmp; if (ch == '"' || ch == '\\' || ch == '(' || ch == ')' || ch == ',' || isspace((unsigned char) ch)) { nq = true; break; } } /* And emit the string */ if (nq) appendStringInfoChar(&buf, '"'); for (tmp = value; *tmp; tmp++) { char ch = *tmp; if (ch == '"' || ch == '\\') appendStringInfoChar(&buf, ch); appendStringInfoChar(&buf, ch); } if (nq) appendStringInfoChar(&buf, '"'); } appendStringInfoChar(&buf, ')'); pfree(values); pfree(nulls); ReleaseTupleDesc(tupdesc); PG_RETURN_CSTRING(buf.data); }
Datum pg_config(PG_FUNCTION_ARGS) { ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; Tuplestorestate *tupstore; HeapTuple tuple; TupleDesc tupdesc; AttInMetadata *attinmeta; MemoryContext per_query_ctx; MemoryContext oldcontext; ConfigData *configdata; size_t configdata_len; char *values[2]; int i = 0; /* check to see if caller supports us returning a tuplestore */ if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("materialize mode required, but it is not " "allowed in this context"))); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); /* get the requested return tuple description */ tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); /* * Check to make sure we have a reasonable tuple descriptor */ if (tupdesc->natts != 2 || tupdesc->attrs[0]->atttypid != TEXTOID || tupdesc->attrs[1]->atttypid != TEXTOID) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("query-specified return tuple and " "function return type are not compatible"))); /* OK to use it */ attinmeta = TupleDescGetAttInMetadata(tupdesc); /* let the caller know we're sending back a tuplestore */ rsinfo->returnMode = SFRM_Materialize; /* initialize our tuplestore */ tupstore = tuplestore_begin_heap(true, false, work_mem); configdata = get_configdata(my_exec_path, &configdata_len); for (i = 0; i < configdata_len; i++) { values[0] = configdata[i].name; values[1] = configdata[i].setting; tuple = BuildTupleFromCStrings(attinmeta, values); tuplestore_puttuple(tupstore, tuple); } /* * no longer need the tuple descriptor reference created by * TupleDescGetAttInMetadata() */ ReleaseTupleDesc(tupdesc); tuplestore_donestoring(tupstore); 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. */ rsinfo->setDesc = tupdesc; MemoryContextSwitchTo(oldcontext); return (Datum) 0; }
/* * Turn a composite / record into JSON. */ static void composite_to_jsonb(Datum composite, JsonbInState *result) { HeapTupleHeader td; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tmptup, *tuple; int i; td = DatumGetHeapTupleHeader(composite); /* Extract rowtype info and find a tupdesc */ tupType = HeapTupleHeaderGetTypeId(td); tupTypmod = HeapTupleHeaderGetTypMod(td); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); /* Build a temporary HeapTuple control structure */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; tuple = &tmptup; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); for (i = 0; i < tupdesc->natts; i++) { Datum val; bool isnull; char *attname; JsonbTypeCategory tcategory; Oid outfuncoid; JsonbValue v; if (tupdesc->attrs[i]->attisdropped) continue; attname = NameStr(tupdesc->attrs[i]->attname); v.type = jbvString; /* don't need checkStringLen here - can't exceed maximum name length */ v.val.string.len = strlen(attname); v.val.string.val = attname; result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); val = heap_getattr(tuple, i + 1, tupdesc, &isnull); if (isnull) { tcategory = JSONBTYPE_NULL; outfuncoid = InvalidOid; } else jsonb_categorize_type(tupdesc->attrs[i]->atttypid, &tcategory, &outfuncoid); datum_to_jsonb(val, isnull, result, tcategory, outfuncoid, false); } result->res = pushJsonbValue(&result->parseState, WJB_END_OBJECT, NULL); ReleaseTupleDesc(tupdesc); }
Datum plr_environ(PG_FUNCTION_ARGS) { ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; Tuplestorestate *tupstore; HeapTuple tuple; TupleDesc tupdesc; AttInMetadata *attinmeta; MemoryContext per_query_ctx; MemoryContext oldcontext; char *var_name; char *var_val; char *values[2]; #ifndef WIN32 char **current_env; #else char *buf; LPTSTR envstr; int count = 0; int i; #endif /* check to see if caller supports us returning a tuplestore */ if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("materialize mode required, but it is not " "allowed in this context"))); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); /* get the requested return tuple description */ tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); /* * Check to make sure we have a reasonable tuple descriptor */ if (tupdesc->natts != 2 || tupdesc->attrs[0]->atttypid != TEXTOID || tupdesc->attrs[1]->atttypid != TEXTOID) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("query-specified return tuple and " "function return type are not compatible"))); /* OK to use it */ attinmeta = TupleDescGetAttInMetadata(tupdesc); /* let the caller know we're sending back a tuplestore */ rsinfo->returnMode = SFRM_Materialize; /* initialize our tuplestore */ tupstore = TUPLESTORE_BEGIN_HEAP; #ifndef WIN32 for (current_env = environ; current_env != NULL && *current_env != NULL; current_env++) { Size name_len; var_val = strchr(*current_env, '='); if (!var_val) continue; name_len = var_val - *current_env; var_name = (char *) palloc0(name_len + 1); memcpy(var_name, *current_env, name_len); values[0] = var_name; values[1] = var_val + 1; tuple = BuildTupleFromCStrings(attinmeta, values); tuplestore_puttuple(tupstore, tuple); pfree(var_name); } #else buf = GetEnvironmentStrings(); envstr = buf; while (true) { if (*envstr == 0) break; while (*envstr != 0) envstr++; envstr++; count++; } /* reset pointer to the environment buffer */ envstr = buf; while(*buf == '=') buf++; for (i = 0; i < count; i++) { Size name_len; var_val = strchr(buf, '='); if (!var_val) continue; name_len = var_val - buf; var_name = (char *) palloc0(name_len + 1); memcpy(var_name, buf, name_len); values[0] = var_name; values[1] = var_val + 1; tuple = BuildTupleFromCStrings(attinmeta, values); tuplestore_puttuple(tupstore, tuple); pfree(var_name); while(*buf != '\0') buf++; buf++; } FreeEnvironmentStrings(envstr); #endif /* * no longer need the tuple descriptor reference created by * TupleDescGetAttInMetadata() */ ReleaseTupleDesc(tupdesc); tuplestore_donestoring(tupstore); 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. */ rsinfo->setDesc = tupdesc; MemoryContextSwitchTo(oldcontext); return (Datum) 0; }
static PyObject * PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc) { PyObject *volatile arg = NULL; PyObject *volatile args = NULL; int i; PG_TRY(); { args = PyList_New(proc->nargs); for (i = 0; i < proc->nargs; i++) { if (proc->args[i].is_rowtype > 0) { if (fcinfo->argnull[i]) arg = NULL; else { HeapTupleHeader td; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tmptup; td = DatumGetHeapTupleHeader(fcinfo->arg[i]); /* Extract rowtype info and find a tupdesc */ tupType = HeapTupleHeaderGetTypeId(td); tupTypmod = HeapTupleHeaderGetTypMod(td); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); /* Set up I/O funcs if not done yet */ if (proc->args[i].is_rowtype != 1) PLy_input_tuple_funcs(&(proc->args[i]), tupdesc); /* Build a temporary HeapTuple control structure */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc); ReleaseTupleDesc(tupdesc); } } else { if (fcinfo->argnull[i]) arg = NULL; else { arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d), fcinfo->arg[i]); } } if (arg == NULL) { Py_INCREF(Py_None); arg = Py_None; } if (PyList_SetItem(args, i, arg) == -1) PLy_elog(ERROR, "PyList_SetItem() failed, while setting up arguments"); if (proc->argnames && proc->argnames[i] && PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1) PLy_elog(ERROR, "PyDict_SetItemString() failed, while setting up arguments"); arg = NULL; } /* Set up output conversion for functions returning RECORD */ if (proc->result.out.d.typoid == RECORDOID) { TupleDesc desc; if (get_call_result_type(fcinfo, NULL, &desc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); /* cache the output conversion functions */ PLy_output_record_funcs(&(proc->result), desc); } } PG_CATCH(); { Py_XDECREF(arg); Py_XDECREF(args); PG_RE_THROW(); } PG_END_TRY(); return args; }
/* * Convert a Python object to a composite type. First look up the type's * description, then route the Python object through the conversion function * for obtaining PostgreSQL tuples. */ static Datum PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv, bool *isnull, bool inarray) { Datum rv; TupleDesc desc; if (plrv == Py_None) { *isnull = true; return (Datum) 0; } *isnull = false; /* * The string conversion case doesn't require a tupdesc, nor per-field * conversion data, so just go for it if that's the case to use. */ if (PyString_Check(plrv) || PyUnicode_Check(plrv)) return PLyString_ToComposite(arg, plrv, inarray); /* * If we're dealing with a named composite type, we must look up the * tupdesc every time, to protect against possible changes to the type. * RECORD types can't change between calls; but we must still be willing * to set up the info the first time, if nobody did yet. */ if (arg->typoid != RECORDOID) { desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod); /* We should have the descriptor of the type's typcache entry */ Assert(desc == arg->u.tuple.typentry->tupDesc); /* Detect change of descriptor, update cache if needed */ if (arg->u.tuple.tupdescseq != arg->u.tuple.typentry->tupDescSeqNo) { PLy_output_setup_tuple(arg, desc, PLy_current_execution_context()->curr_proc); arg->u.tuple.tupdescseq = arg->u.tuple.typentry->tupDescSeqNo; } } else { desc = arg->u.tuple.recdesc; if (desc == NULL) { desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod); arg->u.tuple.recdesc = desc; } else { /* Pin descriptor to match unpin below */ PinTupleDesc(desc); } } /* Simple sanity check on our caching */ Assert(desc->natts == arg->u.tuple.natts); /* * Convert, using the appropriate method depending on the type of the * supplied Python object. */ if (PySequence_Check(plrv)) /* composite type as sequence (tuple, list etc) */ rv = PLySequence_ToComposite(arg, desc, plrv); else if (PyMapping_Check(plrv)) /* composite type as mapping (currently only dict) */ rv = PLyMapping_ToComposite(arg, desc, plrv); else /* returned as smth, must provide method __getattr__(name) */ rv = PLyGenericObject_ToComposite(arg, desc, plrv, inarray); ReleaseTupleDesc(desc); return rv; }
/* * record_send - binary output routine for any composite type. */ Datum record_send(PG_FUNCTION_ARGS) { HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tuple; RecordIOData *my_extra; int ncolumns; int validcols; int i; Datum *values; bool *nulls; StringInfoData buf; /* Extract type info from the tuple itself */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_data = rec; /* * We arrange to look up the needed I/O info just once per series of * calls, assuming the record type doesn't change underneath us. */ my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns != ncolumns) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || my_extra->record_typmod != tupTypmod) { MemSet(my_extra, 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra->record_type = tupType; my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); /* Break down the tuple into fields */ heap_deform_tuple(&tuple, tupdesc, values, nulls); /* And build the result string */ pq_begintypsend(&buf); /* Need to scan to count nondeleted columns */ validcols = 0; for (i = 0; i < ncolumns; i++) { if (!tupdesc->attrs[i]->attisdropped) validcols++; } pq_sendint(&buf, validcols, 4); for (i = 0; i < ncolumns; i++) { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; bytea *outputbytes; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) continue; pq_sendint(&buf, column_type, sizeof(Oid)); if (nulls[i]) { /* emit -1 data length to signify a NULL */ pq_sendint(&buf, -1, 4); continue; } /* * Convert the column value to binary */ if (column_info->column_type != column_type) { bool typIsVarlena; getTypeBinaryOutputInfo(column_type, &column_info->typiofunc, &typIsVarlena); fmgr_info_cxt(column_info->typiofunc, &column_info->proc, fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } outputbytes = SendFunctionCall(&column_info->proc, values[i]); /* We assume the result will not have been toasted */ pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4); pq_sendbytes(&buf, VARDATA(outputbytes), VARSIZE(outputbytes) - VARHDRSZ); pfree(outputbytes); } pfree(values); pfree(nulls); ReleaseTupleDesc(tupdesc); PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); }
Datum hstore_from_record(PG_FUNCTION_ARGS) { HeapTupleHeader rec; int4 buflen; HStore *out; Pairs *pairs; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tuple; RecordIOData *my_extra; int ncolumns; int i, j; Datum *values; bool *nulls; if (PG_ARGISNULL(0)) { Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); /* * have no tuple to look at, so the only source of type info is the * argtype. The lookup_rowtype_tupdesc call below will error out if we * don't have a known composite type oid here. */ tupType = argtype; tupTypmod = -1; rec = NULL; } else { rec = PG_GETARG_HEAPTUPLEHEADER(0); /* Extract type info from the tuple itself */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); } tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; /* * We arrange to look up the needed I/O info just once per series of * calls, assuming the record type doesn't change underneath us. */ my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns != ncolumns) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || my_extra->record_typmod != tupTypmod) { MemSet(my_extra, 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra->record_type = tupType; my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } pairs = palloc(ncolumns * sizeof(Pairs)); if (rec) { /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; #ifdef PGXC tuple.t_xc_node_id = 0; #endif tuple.t_data = rec; values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); /* Break down the tuple into fields */ heap_deform_tuple(&tuple, tupdesc, values, nulls); } else { values = NULL; nulls = NULL; } for (i = 0, j = 0; i < ncolumns; ++i) { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; char *value; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) continue; pairs[j].key = NameStr(tupdesc->attrs[i]->attname); pairs[j].keylen = hstoreCheckKeyLen(strlen(NameStr(tupdesc->attrs[i]->attname))); if (!nulls || nulls[i]) { pairs[j].val = NULL; pairs[j].vallen = 4; pairs[j].isnull = true; pairs[j].needfree = false; ++j; continue; } /* * Convert the column value to text */ if (column_info->column_type != column_type) { bool typIsVarlena; getTypeOutputInfo(column_type, &column_info->typiofunc, &typIsVarlena); fmgr_info_cxt(column_info->typiofunc, &column_info->proc, fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } value = OutputFunctionCall(&column_info->proc, values[i]); pairs[j].val = value; pairs[j].vallen = hstoreCheckValLen(strlen(value)); pairs[j].isnull = false; pairs[j].needfree = false; ++j; } ncolumns = hstoreUniquePairs(pairs, j, &buflen); out = hstorePairs(pairs, ncolumns, buflen); ReleaseTupleDesc(tupdesc); PG_RETURN_POINTER(out); }
Datum hstore_populate_record(PG_FUNCTION_ARGS) { Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); HStore *hs; HEntry *entries; char *ptr; HeapTupleHeader rec; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tuple; HeapTuple rettuple; RecordIOData *my_extra; int ncolumns; int i; Datum *values; bool *nulls; if (!type_is_rowtype(argtype)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("first argument must be a rowtype"))); if (PG_ARGISNULL(0)) { if (PG_ARGISNULL(1)) PG_RETURN_NULL(); rec = NULL; /* * have no tuple to look at, so the only source of type info is the * argtype. The lookup_rowtype_tupdesc call below will error out if we * don't have a known composite type oid here. */ tupType = argtype; tupTypmod = -1; } else { rec = PG_GETARG_HEAPTUPLEHEADER(0); if (PG_ARGISNULL(1)) PG_RETURN_POINTER(rec); /* Extract type info from the tuple itself */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); } hs = PG_GETARG_HS(1); entries = ARRPTR(hs); ptr = STRPTR(hs); /* * if the input hstore is empty, we can only skip the rest if we were * passed in a non-null record, since otherwise there may be issues with * domain nulls. */ if (HS_COUNT(hs) == 0 && rec) PG_RETURN_POINTER(rec); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; if (rec) { /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; #ifdef PGXC tuple.t_xc_node_id = 0; #endif tuple.t_data = rec; } /* * We arrange to look up the needed I/O info just once per series of * calls, assuming the record type doesn't change underneath us. */ my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns != ncolumns) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || my_extra->record_typmod != tupTypmod) { MemSet(my_extra, 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra->record_type = tupType; my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); if (rec) { /* Break down the tuple into fields */ heap_deform_tuple(&tuple, tupdesc, values, nulls); } else { for (i = 0; i < ncolumns; ++i) { values[i] = (Datum) 0; nulls[i] = true; } } for (i = 0; i < ncolumns; ++i) { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; char *value; int idx; int vallen; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) { nulls[i] = true; continue; } idx = hstoreFindKey(hs, 0, NameStr(tupdesc->attrs[i]->attname), strlen(NameStr(tupdesc->attrs[i]->attname))); /* * we can't just skip here if the key wasn't found since we might have * a domain to deal with. If we were passed in a non-null record * datum, we assume that the existing values are valid (if they're * not, then it's not our fault), but if we were passed in a null, * then every field which we don't populate needs to be run through * the input function just in case it's a domain type. */ if (idx < 0 && rec) continue; /* * Prepare to convert the column value from text */ if (column_info->column_type != column_type) { getTypeInputInfo(column_type, &column_info->typiofunc, &column_info->typioparam); fmgr_info_cxt(column_info->typiofunc, &column_info->proc, fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } if (idx < 0 || HS_VALISNULL(entries, idx)) { /* * need InputFunctionCall to happen even for nulls, so that domain * checks are done */ values[i] = InputFunctionCall(&column_info->proc, NULL, column_info->typioparam, tupdesc->attrs[i]->atttypmod); nulls[i] = true; } else { vallen = HS_VALLEN(entries, idx); value = palloc(1 + vallen); memcpy(value, HS_VAL(entries, ptr, idx), vallen); value[vallen] = 0; values[i] = InputFunctionCall(&column_info->proc, value, column_info->typioparam, tupdesc->attrs[i]->atttypmod); nulls[i] = false; } } rettuple = heap_form_tuple(tupdesc, values, nulls); ReleaseTupleDesc(tupdesc); PG_RETURN_DATUM(HeapTupleGetDatum(rettuple)); }
/* * SQL function json_populate_record * * set fields in a record from the argument json * * Code adapted shamelessly from hstore's populate_record * which is in turn partly adapted from record_out. * * The json is decomposed into a hash table, in which each * field in the record is then looked up by name. */ Datum json_populate_record(PG_FUNCTION_ARGS) { Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); text *json; bool use_json_as_text; HTAB *json_hash; HeapTupleHeader rec; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tuple; HeapTuple rettuple; RecordIOData *my_extra; int ncolumns; int i; Datum *values; bool *nulls; char fname[NAMEDATALEN]; JsonHashEntry hashentry; use_json_as_text = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2); if (!type_is_rowtype(argtype)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("first argument must be a rowtype"))); if (PG_ARGISNULL(0)) { if (PG_ARGISNULL(1)) PG_RETURN_NULL(); rec = NULL; /* * have no tuple to look at, so the only source of type info is the * argtype. The lookup_rowtype_tupdesc call below will error out if we * don't have a known composite type oid here. */ tupType = argtype; tupTypmod = -1; } else { rec = PG_GETARG_HEAPTUPLEHEADER(0); if (PG_ARGISNULL(1)) PG_RETURN_POINTER(rec); /* Extract type info from the tuple itself */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); } json = PG_GETARG_TEXT_P(1); json_hash = get_json_object_as_hash(json, "json_populate_record", use_json_as_text); /* * if the input json is empty, we can only skip the rest if we were passed * in a non-null record, since otherwise there may be issues with domain * nulls. */ if (hash_get_num_entries(json_hash) == 0 && rec) PG_RETURN_POINTER(rec); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; if (rec) { /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; tuple.t_data = rec; } /* * We arrange to look up the needed I/O info just once per series of * calls, assuming the record type doesn't change underneath us. */ my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns != ncolumns) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || my_extra->record_typmod != tupTypmod) { MemSet(my_extra, 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra->record_type = tupType; my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); if (rec) { /* Break down the tuple into fields */ heap_deform_tuple(&tuple, tupdesc, values, nulls); } else { for (i = 0; i < ncolumns; ++i) { values[i] = (Datum) 0; nulls[i] = true; } } for (i = 0; i < ncolumns; ++i) { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; char *value; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) { nulls[i] = true; continue; } memset(fname, 0, NAMEDATALEN); strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN); hashentry = hash_search(json_hash, fname, HASH_FIND, NULL); /* * we can't just skip here if the key wasn't found since we might have * a domain to deal with. If we were passed in a non-null record * datum, we assume that the existing values are valid (if they're * not, then it's not our fault), but if we were passed in a null, * then every field which we don't populate needs to be run through * the input function just in case it's a domain type. */ if (hashentry == NULL && rec) continue; /* * Prepare to convert the column value from text */ if (column_info->column_type != column_type) { getTypeInputInfo(column_type, &column_info->typiofunc, &column_info->typioparam); fmgr_info_cxt(column_info->typiofunc, &column_info->proc, fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } if (hashentry == NULL || hashentry->isnull) { /* * need InputFunctionCall to happen even for nulls, so that domain * checks are done */ values[i] = InputFunctionCall(&column_info->proc, NULL, column_info->typioparam, tupdesc->attrs[i]->atttypmod); nulls[i] = true; } else { value = hashentry->val; values[i] = InputFunctionCall(&column_info->proc, value, column_info->typioparam, tupdesc->attrs[i]->atttypmod); nulls[i] = false; } } rettuple = heap_form_tuple(tupdesc, values, nulls); ReleaseTupleDesc(tupdesc); PG_RETURN_DATUM(HeapTupleGetDatum(rettuple)); }
/* * record_eq : * compares two records for equality * result : * returns true if the records are equal, false otherwise. * * Note: we do not use record_cmp here, since equality may be meaningful in * datatypes that don't have a total ordering (and hence no btree support). */ Datum record_eq(PG_FUNCTION_ARGS) { HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); bool result = true; Oid tupType1; Oid tupType2; int32 tupTypmod1; int32 tupTypmod2; TupleDesc tupdesc1; TupleDesc tupdesc2; HeapTupleData tuple1; HeapTupleData tuple2; int ncolumns1; int ncolumns2; RecordCompareData *my_extra; int ncols; Datum *values1; Datum *values2; bool *nulls1; bool *nulls2; int i1; int i2; int j; /* Extract type info from the tuples */ tupType1 = HeapTupleHeaderGetTypeId(record1); tupTypmod1 = HeapTupleHeaderGetTypMod(record1); tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1); ncolumns1 = tupdesc1->natts; tupType2 = HeapTupleHeaderGetTypeId(record2); tupTypmod2 = HeapTupleHeaderGetTypMod(record2); tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2); ncolumns2 = tupdesc2->natts; /* Build temporary HeapTuple control structures */ tuple1.t_len = HeapTupleHeaderGetDatumLength(record1); ItemPointerSetInvalid(&(tuple1.t_self)); tuple1.t_tableOid = InvalidOid; tuple1.t_data = record1; tuple2.t_len = HeapTupleHeaderGetDatumLength(record2); ItemPointerSetInvalid(&(tuple2.t_self)); tuple2.t_tableOid = InvalidOid; tuple2.t_data = record2; /* * We arrange to look up the needed comparison info just once per series * of calls, assuming the record types don't change underneath us. */ ncols = Max(ncolumns1, ncolumns2); my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns < ncols) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordCompareData) - sizeof(ColumnCompareData) + ncols * sizeof(ColumnCompareData)); my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; my_extra->ncolumns = ncols; my_extra->record1_type = InvalidOid; my_extra->record1_typmod = 0; my_extra->record2_type = InvalidOid; my_extra->record2_typmod = 0; } if (my_extra->record1_type != tupType1 || my_extra->record1_typmod != tupTypmod1 || my_extra->record2_type != tupType2 || my_extra->record2_typmod != tupTypmod2) { MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData)); my_extra->record1_type = tupType1; my_extra->record1_typmod = tupTypmod1; my_extra->record2_type = tupType2; my_extra->record2_typmod = tupTypmod2; } /* Break down the tuples into fields */ values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum)); nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool)); heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1); values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum)); nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool)); heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); /* * Scan corresponding columns, allowing for dropped columns in different * places in the two rows. i1 and i2 are physical column indexes, j is * the logical column index. */ i1 = i2 = j = 0; while (i1 < ncolumns1 || i2 < ncolumns2) { TypeCacheEntry *typentry; FunctionCallInfoData locfcinfo; bool oprresult; /* * Skip dropped columns */ if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped) { i1++; continue; } if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped) { i2++; continue; } if (i1 >= ncolumns1 || i2 >= ncolumns2) break; /* we'll deal with mismatch below loop */ /* * Have two matching columns, they must be same type */ if (tupdesc1->attrs[i1]->atttypid != tupdesc2->attrs[i2]->atttypid) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot compare dissimilar column types %s and %s at record column %d", format_type_be(tupdesc1->attrs[i1]->atttypid), format_type_be(tupdesc2->attrs[i2]->atttypid), j + 1))); /* * Lookup the equality function if not done already */ typentry = my_extra->columns[j].typentry; if (typentry == NULL || typentry->type_id != tupdesc1->attrs[i1]->atttypid) { typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid, 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(typentry->type_id)))); my_extra->columns[j].typentry = typentry; } /* * We consider two NULLs equal; NULL > not-NULL. */ if (!nulls1[i1] || !nulls2[i2]) { if (nulls1[i1] || nulls2[i2]) { result = false; break; } /* Compare the pair of elements */ InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2, NULL, NULL); locfcinfo.arg[0] = values1[i1]; locfcinfo.arg[1] = values2[i2]; locfcinfo.argnull[0] = false; locfcinfo.argnull[1] = false; locfcinfo.isnull = false; oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo)); if (!oprresult) { result = false; break; } } /* equal, so continue to next column */ i1++, i2++, j++; } /* * If we didn't break out of the loop early, check for column count * mismatch. (We do not report such mismatch if we found unequal column * values; is that a feature or a bug?) */ if (result) { if (i1 != ncolumns1 || i2 != ncolumns2) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot compare record types with different numbers of columns"))); } pfree(values1); pfree(nulls1); pfree(values2); pfree(nulls2); ReleaseTupleDesc(tupdesc1); ReleaseTupleDesc(tupdesc2); /* Avoid leaking memory when handed toasted input. */ PG_FREE_IF_COPY(record1, 0); PG_FREE_IF_COPY(record2, 1); PG_RETURN_BOOL(result); }
Datum make_tuple_indirect(PG_FUNCTION_ARGS) { HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); HeapTupleData tuple; int ncolumns; Datum *values; bool *nulls; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTuple newtup; int i; MemoryContext old_context; /* Extract type info from the tuple itself */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; tuple.t_data = rec; values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); heap_deform_tuple(&tuple, tupdesc, values, nulls); old_context = MemoryContextSwitchTo(TopTransactionContext); for (i = 0; i < ncolumns; i++) { struct varlena *attr; struct varlena *new_attr; struct varatt_indirect redirect_pointer; /* only work on existing, not-null varlenas */ if (tupdesc->attrs[i]->attisdropped || nulls[i] || tupdesc->attrs[i]->attlen != -1) continue; attr = (struct varlena *) DatumGetPointer(values[i]); /* don't recursively indirect */ if (VARATT_IS_EXTERNAL_INDIRECT(attr)) continue; /* copy datum, so it still lives later */ if (VARATT_IS_EXTERNAL_ONDISK(attr)) attr = heap_tuple_fetch_attr(attr); else { struct varlena *oldattr = attr; attr = palloc0(VARSIZE_ANY(oldattr)); memcpy(attr, oldattr, VARSIZE_ANY(oldattr)); } /* build indirection Datum */ new_attr = (struct varlena *) palloc0(INDIRECT_POINTER_SIZE); redirect_pointer.pointer = attr; SET_VARTAG_EXTERNAL(new_attr, VARTAG_INDIRECT); memcpy(VARDATA_EXTERNAL(new_attr), &redirect_pointer, sizeof(redirect_pointer)); values[i] = PointerGetDatum(new_attr); } newtup = heap_form_tuple(tupdesc, values, nulls); pfree(values); pfree(nulls); ReleaseTupleDesc(tupdesc); MemoryContextSwitchTo(old_context); /* * We intentionally don't use PG_RETURN_HEAPTUPLEHEADER here, because that * would cause the indirect toast pointers to be flattened out of the * tuple immediately, rendering subsequent testing irrelevant. So just * return the HeapTupleHeader pointer as-is. This violates the general * rule that composite Datums shouldn't contain toast pointers, but so * long as the regression test scripts don't insert the result of this * function into a container type (record, array, etc) it should be OK. */ PG_RETURN_POINTER(newtup->t_data); }
/* * record_recv - binary input routine for any composite type. */ Datum record_recv(PG_FUNCTION_ARGS) { StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); Oid tupType = PG_GETARG_OID(1); #ifdef NOT_USED int32 typmod = PG_GETARG_INT32(2); #endif HeapTupleHeader result; int32 tupTypmod; TupleDesc tupdesc; HeapTuple tuple; RecordIOData *my_extra; int ncolumns; int usercols; int validcols; int i; Datum *values; bool *nulls; /* * Use the passed type unless it's RECORD; we can't support input of * anonymous types, mainly because there's no good way to figure out which * anonymous type is wanted. Note that for RECORD, what we'll probably * actually get is RECORD's typelem, ie, zero. */ if (tupType == InvalidOid || tupType == RECORDOID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("input of anonymous composite types is not implemented"))); tupTypmod = -1; /* for all non-anonymous types */ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; /* * We arrange to look up the needed I/O info just once per series of * calls, assuming the record type doesn't change underneath us. */ my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns != ncolumns) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || my_extra->record_typmod != tupTypmod) { MemSet(my_extra, 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra->record_type = tupType; my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); /* Fetch number of columns user thinks it has */ usercols = pq_getmsgint(buf, 4); /* Need to scan to count nondeleted columns */ validcols = 0; for (i = 0; i < ncolumns; i++) { if (!tupdesc->attrs[i]->attisdropped) validcols++; } if (usercols != validcols) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("wrong number of columns: %d, expected %d", usercols, validcols))); /* Process each column */ for (i = 0; i < ncolumns; i++) { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; Oid coltypoid; int itemlen; StringInfoData item_buf; StringInfo bufptr; char csave; /* Ignore dropped columns in datatype, but fill with nulls */ if (tupdesc->attrs[i]->attisdropped) { values[i] = (Datum) 0; nulls[i] = true; continue; } /* Verify column datatype */ coltypoid = pq_getmsgint(buf, sizeof(Oid)); if (coltypoid != column_type) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("wrong data type: %u, expected %u", coltypoid, column_type))); /* Get and check the item length */ itemlen = pq_getmsgint(buf, 4); if (itemlen < -1 || itemlen > (buf->len - buf->cursor)) ereport(ERROR, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), errmsg("insufficient data left in message"))); if (itemlen == -1) { /* -1 length means NULL */ bufptr = NULL; nulls[i] = true; csave = 0; /* keep compiler quiet */ } else { /* * Rather than copying data around, we just set up a phony * StringInfo pointing to the correct portion of the input buffer. * We assume we can scribble on the input buffer so as to maintain * the convention that StringInfos have a trailing null. */ item_buf.data = &buf->data[buf->cursor]; item_buf.maxlen = itemlen + 1; item_buf.len = itemlen; item_buf.cursor = 0; buf->cursor += itemlen; csave = buf->data[buf->cursor]; buf->data[buf->cursor] = '\0'; bufptr = &item_buf; nulls[i] = false; } /* Now call the column's receiveproc */ if (column_info->column_type != column_type) { getTypeBinaryInputInfo(column_type, &column_info->typiofunc, &column_info->typioparam); fmgr_info_cxt(column_info->typiofunc, &column_info->proc, fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } values[i] = ReceiveFunctionCall(&column_info->proc, bufptr, column_info->typioparam, tupdesc->attrs[i]->atttypmod); if (bufptr) { /* Trouble if it didn't eat the whole buffer */ if (item_buf.cursor != itemlen) ereport(ERROR, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), errmsg("improper binary format in record column %d", i + 1))); buf->data[buf->cursor] = csave; } } tuple = heap_form_tuple(tupdesc, values, nulls); /* * We cannot return tuple->t_data because heap_form_tuple allocates it as * part of a larger chunk, and our caller may expect to be able to pfree * our result. So must copy the info into a new palloc chunk. */ result = (HeapTupleHeader) palloc(tuple->t_len); memcpy(result, tuple->t_data, tuple->t_len); heap_freetuple(tuple); pfree(values); pfree(nulls); ReleaseTupleDesc(tupdesc); PG_RETURN_HEAPTUPLEHEADER(result); }
void yorder_get_order(Datum eorder,Torder *orderp) { bool isnull; HeapTupleHeader tuple = ((HeapTupleHeader) PG_DETOAST_DATUM(eorder)); Oid tupType; int32 tupTypmod; TupleDesc tupDesc; HeapTupleData tmptup; BOX *p; tupType = HeapTupleHeaderGetTypeId(tuple); tupTypmod = HeapTupleHeaderGetTypMod(tuple); tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); ItemPointerSetInvalid(&(tmptup.t_self)); tmptup.t_tableOid = InvalidOid; tmptup.t_data = tuple; orderp->type = DatumGetInt32(heap_getattr(&tmptup,1,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field type is null in yorder_get_order"))); if(!ORDER_TYPE_IS_VALID(orderp->type)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("order type incorrect in yorder_get_order"))); orderp->id = DatumGetInt32(heap_getattr(&tmptup,2,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field id is null in yorder_get_order"))); orderp->own = DatumGetInt32(heap_getattr(&tmptup,3,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field own is null in yorder_get_order"))); orderp->oid = DatumGetInt32(heap_getattr(&tmptup,4,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field oid is null in yorder_get_order"))); orderp->qtt_requ = DatumGetInt64(heap_getattr(&tmptup,5,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field qtt_requ is null in yorder_get_order"))); //orderp->qua_requ = (HStore *) PG_DETOAST_DATUM(heap_getattr(&tmptup,6,tupDesc,&isnull)); orderp->qua_requ = (Datum) PG_DETOAST_DATUM(heap_getattr(&tmptup,6,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field qua_requ is null in yorder_get_order"))); orderp->qtt_prov = DatumGetInt64(heap_getattr(&tmptup,7,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field qtt_prov is null in yorder_get_order"))); //orderp->qua_prov = (HStore *) PG_DETOAST_DATUM(heap_getattr(&tmptup,8,tupDesc,&isnull)); orderp->qua_prov = (Datum) PG_DETOAST_DATUM(heap_getattr(&tmptup,8,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field qua_prov is null in yorder_get_order"))); orderp->qtt = DatumGetInt64(heap_getattr(&tmptup,9,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field qtt is null in yorder_get_order"))); // pos_requ box, p = DatumGetBoxP(heap_getattr(&tmptup,10,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field pos_requ is null in yorder_get_order"))); GL_CHECK_BOX_S0(p); orderp->pos_requ.x = p->low.x; orderp->pos_requ.y = p->low.y; // pos_prov box, p = DatumGetBoxP(heap_getattr(&tmptup,11,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field pos_prov is null in yorder_get_order"))); GL_CHECK_BOX_S0(p); orderp->pos_prov.x = p->low.x; orderp->pos_prov.y = p->low.y; // dist flat orderp->dist = DatumGetFloat8(heap_getattr(&tmptup,12,tupDesc,&isnull)); if(isnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("the field dist is null in yorder_get_order"))); ReleaseTupleDesc(tupDesc); return; }
/* * record_in - input routine for any composite type. */ Datum record_in(PG_FUNCTION_ARGS) { char *string = PG_GETARG_CSTRING(0); Oid tupType = PG_GETARG_OID(1); #ifdef NOT_USED int32 typmod = PG_GETARG_INT32(2); #endif HeapTupleHeader result; int32 tupTypmod; TupleDesc tupdesc; HeapTuple tuple; RecordIOData *my_extra; bool needComma = false; int ncolumns; int i; char *ptr; Datum *values; bool *nulls; StringInfoData buf; /* * Use the passed type unless it's RECORD; we can't support input of * anonymous types, mainly because there's no good way to figure out which * anonymous type is wanted. Note that for RECORD, what we'll probably * actually get is RECORD's typelem, ie, zero. */ if (tupType == InvalidOid || tupType == RECORDOID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("input of anonymous composite types is not implemented"))); tupTypmod = -1; /* for all non-anonymous types */ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; /* * We arrange to look up the needed I/O info just once per series of * calls, assuming the record type doesn't change underneath us. */ my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns != ncolumns) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || my_extra->record_typmod != tupTypmod) { MemSet(my_extra, 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra->record_type = tupType; my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); /* * Scan the string. We use "buf" to accumulate the de-quoted data for * each column, which is then fed to the appropriate input converter. */ ptr = string; /* Allow leading whitespace */ while (*ptr && isspace((unsigned char) *ptr)) ptr++; if (*ptr++ != '(') { ReleaseTupleDesc(tupdesc); ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Missing left parenthesis."))); } initStringInfo(&buf); for (i = 0; i < ncolumns; i++) { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; char *column_data; /* Ignore dropped columns in datatype, but fill with nulls */ if (tupdesc->attrs[i]->attisdropped) { values[i] = (Datum) 0; nulls[i] = true; continue; } if (needComma) { /* Skip comma that separates prior field from this one */ if (*ptr == ',') ptr++; else { ReleaseTupleDesc(tupdesc); /* *ptr must be ')' */ ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Too few columns."))); } } /* Check for null: completely empty input means null */ if (*ptr == ',' || *ptr == ')') { column_data = NULL; nulls[i] = true; } else { /* Extract string for this column */ bool inquote = false; buf.len = 0; buf.data[0] = '\0'; while (inquote || !(*ptr == ',' || *ptr == ')')) { char ch = *ptr++; if (ch == '\0') { ReleaseTupleDesc(tupdesc); ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Unexpected end of input."))); } if (ch == '\\') { if (*ptr == '\0') { ReleaseTupleDesc(tupdesc); ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Unexpected end of input."))); } appendStringInfoChar(&buf, *ptr++); } else if (ch == '\"') { if (!inquote) inquote = true; else if (*ptr == '\"') { /* doubled quote within quote sequence */ appendStringInfoChar(&buf, *ptr++); } else inquote = false; } else appendStringInfoChar(&buf, ch); } column_data = buf.data; nulls[i] = false; } /* * Convert the column value */ if (column_info->column_type != column_type) { getTypeInputInfo(column_type, &column_info->typiofunc, &column_info->typioparam); fmgr_info_cxt(column_info->typiofunc, &column_info->proc, fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } values[i] = InputFunctionCall(&column_info->proc, column_data, column_info->typioparam, tupdesc->attrs[i]->atttypmod); /* * Prep for next column */ needComma = true; } if (*ptr++ != ')') { ReleaseTupleDesc(tupdesc); ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Too many columns."))); } /* Allow trailing whitespace */ while (*ptr && isspace((unsigned char) *ptr)) ptr++; if (*ptr) { ReleaseTupleDesc(tupdesc); ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed record literal: \"%s\"", string), errdetail("Junk after right parenthesis."))); } tuple = heap_form_tuple(tupdesc, values, nulls); /* * We cannot return tuple->t_data because heap_form_tuple allocates it as * part of a larger chunk, and our caller may expect to be able to pfree * our result. So must copy the info into a new palloc chunk. */ result = (HeapTupleHeader) palloc(tuple->t_len); memcpy(result, tuple->t_data, tuple->t_len); heap_freetuple(tuple); pfree(buf.data); pfree(values); pfree(nulls); ReleaseTupleDesc(tupdesc); PG_RETURN_HEAPTUPLEHEADER(result); }
static void *uri_char(HeapTupleHeader ud, bool hdr, bool term) { TupleDesc td; HeapTupleData tuple; Datum d[URI_LEN]; bool n[URI_LEN]; text *scheme = NULL, *host = NULL, *path = NULL; int16 port; char portbuf[8]; unsigned schemelen = 0, hostlen = 0, portlen = 0, pathlen = 0; unsigned len; void *out; char *p; td = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(ud), HeapTupleHeaderGetTypMod(ud)); tuple.t_len = HeapTupleHeaderGetDatumLength(ud); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; tuple.t_data = ud; heap_deform_tuple(&tuple, td, d, n); ReleaseTupleDesc(td); if (!n[URI_SCHEME]) { scheme = DatumGetTextP(d[URI_SCHEME]); schemelen = VARSIZE_ANY_EXHDR(scheme); } if (!n[URI_HOST]) { host = DatumGetTextP(d[URI_HOST]); hostlen = VARSIZE_ANY_EXHDR(host); } if (!n[URI_PORT]) { port = DatumGetInt16(d[URI_PORT]); portlen = snprintf(portbuf, sizeof(portbuf)-1, ":%hu", port); } if (!n[URI_PATH]) { path = DatumGetTextP(d[URI_PATH]); pathlen = VARSIZE_ANY_EXHDR(path); } len = (hdr ? VARHDRSZ : 0) + schemelen + (scheme ? 3 : 0) + hostlen + portlen + pathlen + term; out = palloc(len); if (hdr) SET_VARSIZE(out, len); p = hdr ? VARDATA(out) : out; if (scheme) { memcpy(p, VARDATA(scheme), schemelen); p += schemelen; *p++ = ':'; *p++ = '/'; *p++ = '/'; } if (host) { domainname_flip(p, VARDATA(host), hostlen); p += hostlen; } memcpy(p, portbuf, portlen); p += portlen; if (path) { memcpy(p, VARDATA(path), pathlen); p += pathlen; } if (term) *p = '\0'; return out; }
/** * @brief Initialize a ParallelWriter */ static void ParallelWriterInit(ParallelWriter *self) { unsigned queryKey; char queueName[MAXPGPATH]; PGresult *res; Assert(self->base.truncate == false); if (self->base.relid != InvalidOid) { TupleDesc resultDesc; /* open relation without any relation locks */ self->base.rel = heap_open(self->base.relid, NoLock); self->base.desc = RelationGetDescr(self->base.rel); self->base.tchecker = CreateTupleChecker(self->base.desc); self->base.tchecker->checker = (CheckerTupleProc) CoercionCheckerTuple; /* * If the return value of the filter function or input function is a * target table, lookup_rowtype_tupdesc grab AccessShareLock on the * table in the first call. We call lookup_rowtype_tupdesc here to * avoid deadlock when lookup_rowtype_tupdesc is called by the internal * routine of the filter function or input function, because a parallel * writer process holds an AccessExclusiveLock. */ resultDesc = lookup_rowtype_tupdesc(self->base.desc->tdtypeid, -1); ReleaseTupleDesc(resultDesc); } else { self->writer->init(self->writer); self->base.desc = self->writer->desc; self->base.tchecker = self->writer->tchecker; } self->base.context = AllocSetContextCreate( CurrentMemoryContext, "ParallelWriter", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* create queue */ self->queue = QueueCreate(&queryKey, DEFAULT_BUFFER_SIZE); snprintf(queueName, lengthof(queueName), ":%u", queryKey); /* connect to localhost */ self->conn = connect_to_localhost(); /* start transaction */ res = PQexec(self->conn, "BEGIN"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { ereport(ERROR, (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), errmsg("could not start transaction"), errdetail("%s", finish_and_get_message(self)))); } PQclear(res); if (!self->writer->dup_badfile) self->writer->dup_badfile = self->base.dup_badfile; if (1 != self->writer->sendQuery(self->writer, self->conn, queueName, self->base.logfile, self->base.verbose)) { ereport(ERROR, (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), errmsg("could not send query"), errdetail("%s", finish_and_get_message(self)))); } }
Datum serialize_record( PG_FUNCTION_ARGS ) { // FILE* log; // log = fopen("/var/lib/postgresql/serializer.log", "a"); HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); HeapTupleData tuple; bool needComma = false; int i; Datum *values; bool *nulls; StringInfoData buf; char *conversion_buf; /* Extract type info from the tuple itself */ Oid tupType = HeapTupleHeaderGetTypeId(rec); int32 tupTypmod = HeapTupleHeaderGetTypMod(rec); TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); int ncolumns = tupdesc->natts; /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; tuple.t_data = rec; // fprintf(log, "Doing serialize_record\n"); // fflush(log); values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); /* Break down the tuple into fields */ heap_deform_tuple(&tuple, tupdesc, values, nulls); /* And build the result string */ initStringInfo(&buf); appendStringInfoChar(&buf, '{'); for (i = 0; i < ncolumns; i++) { Oid column_type = tupdesc->attrs[ i ]->atttypid; char *value; char *column_name; char type_category; HeapTuple type_tuple; FmgrInfo flinfo; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) continue; if (nulls[i]) { /* emit nothing... */ continue; } if (needComma) appendStringInfoChar(&buf, ','); needComma = true; /* obtain column name */ column_name = SPI_fname( tupdesc, i + 1 ); /* obtain type information from pg_catalog */ type_tuple = SearchSysCache1( TYPEOID, ObjectIdGetDatum(column_type) ); if (!HeapTupleIsValid( type_tuple )) elog(ERROR, "cache lookup failed for relation %u", column_type); type_category = ((Form_pg_type) GETSTRUCT( type_tuple ))->typcategory; ReleaseSysCache( type_tuple ); /* append column name */ appendStringInfoChar(&buf, '"'); appendStringInfoString(&buf, column_name); appendStringInfoString(&buf, "\":"); switch( type_category ) { // http://www.postgresql.org/docs/current/static/catalog-pg-type.html#CATALOG-TYPCATEGORY-TABLE case 'A': //array //call to serialize_array( ... ) MemSet( &flinfo, 0, sizeof( flinfo ) ); flinfo.fn_addr = serialize_array; flinfo.fn_nargs = 1; flinfo.fn_mcxt = fcinfo->flinfo->fn_mcxt; value = PG_TEXT_DATUM_GET_CSTR( FunctionCall1( &flinfo, values[ i ] ) ); appendStringInfoString(&buf, value); break; case 'C': //composite //recursive call to serialize_record( ... ) MemSet( &flinfo, 0, sizeof( flinfo ) ); flinfo.fn_addr = serialize_record; flinfo.fn_nargs = 1; flinfo.fn_mcxt = fcinfo->flinfo->fn_mcxt; value = PG_TEXT_DATUM_GET_CSTR( FunctionCall1( &flinfo, values[ i ] ) ); appendStringInfoString(&buf, value); break; case 'N': //numeric conversion_buf = NULL; // get column text value // fprintf(log, "Calling ConvertToText\n"); // fflush(log); value = ConvertToText( values[ i ], column_type, fcinfo->flinfo->fn_mcxt, &conversion_buf ); // fprintf(log, "ConvertToText succeded\n"); // fflush(log); appendStringInfoString(&buf, value); // fprintf(log, "append.... succeded\n"); // fflush(log); if(conversion_buf != NULL) { pfree(conversion_buf); conversion_buf = NULL; } break; case 'B': //boolean appendStringInfoString(&buf, // get column boolean value DatumGetBool( values[ i ] ) ? "true" : "false" ); break; default: //another conversion_buf = NULL; // get column text value // fprintf(log, "Calling ConvertToText\n"); // fflush(log); value = ConvertToText( values[ i ], column_type, fcinfo->flinfo->fn_mcxt, &conversion_buf ); // fprintf(log, "ConvertToText succeded\n"); // fflush(log); appendStringInfoQuotedString(&buf, value); // fprintf(log, "append.... succeded\n"); // fflush(log); if(conversion_buf != NULL) { pfree(conversion_buf); conversion_buf = NULL; } } } appendStringInfoChar(&buf, '}'); pfree(values); pfree(nulls); ReleaseTupleDesc(tupdesc); // fclose(log); PG_RETURN_TEXT_P( PG_CSTR_GET_TEXT( buf.data ) ); }
/* * Turn a composite / record into JSON. */ static void composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) { HeapTupleHeader td; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tmptup, *tuple; int i; bool needsep = false; char *sep; sep = use_line_feeds ? ",\n " : ","; td = DatumGetHeapTupleHeader(composite); /* Extract rowtype info and find a tupdesc */ tupType = HeapTupleHeaderGetTypeId(td); tupTypmod = HeapTupleHeaderGetTypMod(td); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); /* Build a temporary HeapTuple control structure */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; tuple = &tmptup; appendStringInfoChar(result,'{'); for (i = 0; i < tupdesc->natts; i++) { Datum val, origval; bool isnull; char *attname; TYPCATEGORY tcategory; Oid typoutput; bool typisvarlena; if (tupdesc->attrs[i]->attisdropped) continue; if (needsep) appendStringInfoString(result,sep); needsep = true; attname = NameStr(tupdesc->attrs[i]->attname); escape_json(result,attname); appendStringInfoChar(result,':'); origval = heap_getattr(tuple, i + 1, tupdesc, &isnull); if (tupdesc->attrs[i]->atttypid == RECORDARRAYOID) tcategory = TYPCATEGORY_ARRAY; else if (tupdesc->attrs[i]->atttypid == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; else tcategory = TypeCategory(tupdesc->attrs[i]->atttypid); getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &typoutput, &typisvarlena); /* * If we have a toasted datum, forcibly detoast it here to avoid memory * leakage inside the type's output routine. */ if (typisvarlena && ! isnull) val = PointerGetDatum(PG_DETOAST_DATUM(origval)); else val = origval; datum_to_json(val, result, tcategory, typoutput); /* Clean up detoasted copy, if any */ if (val != origval) pfree(DatumGetPointer(val)); } appendStringInfoChar(result,'}'); ReleaseTupleDesc(tupdesc); }
/* function subhandler */ Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc) { Datum rv; PyObject *volatile plargs = NULL; PyObject *volatile plrv = NULL; ErrorContextCallback plerrcontext; PG_TRY(); { if (!proc->is_setof || proc->setof == NULL) { /* * Simple type returning function or first time for SETOF * function: actually execute the function. */ plargs = PLy_function_build_args(fcinfo, proc); plrv = PLy_procedure_call(proc, "args", plargs); if (!proc->is_setof) { /* * SETOF function parameters will be deleted when last row is * returned */ PLy_function_delete_args(proc); } Assert(plrv != NULL); } /* * If it returns a set, call the iterator to get the next return item. * We stay in the SPI context while doing this, because PyIter_Next() * calls back into Python code which might contain SPI calls. */ if (proc->is_setof) { bool has_error = false; ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; if (proc->setof == NULL) { /* first time -- do checks and setup */ if (!rsi || !IsA(rsi, ReturnSetInfo) || (rsi->allowedModes & SFRM_ValuePerCall) == 0) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("unsupported set function return mode"), errdetail("PL/Python set-returning functions only support returning only value per call."))); } rsi->returnMode = SFRM_ValuePerCall; /* Make iterator out of returned object */ proc->setof = PyObject_GetIter(plrv); Py_DECREF(plrv); plrv = NULL; if (proc->setof == NULL) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("returned object cannot be iterated"), errdetail("PL/Python set-returning functions must return an iterable object."))); } /* Fetch next from iterator */ plrv = PyIter_Next(proc->setof); if (plrv) rsi->isDone = ExprMultipleResult; else { rsi->isDone = ExprEndResult; has_error = PyErr_Occurred() != NULL; } if (rsi->isDone == ExprEndResult) { /* Iterator is exhausted or error happened */ Py_DECREF(proc->setof); proc->setof = NULL; Py_XDECREF(plargs); Py_XDECREF(plrv); PLy_function_delete_args(proc); if (has_error) PLy_elog(ERROR, "error fetching next item from iterator"); /* Disconnect from the SPI manager before returning */ if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); fcinfo->isnull = true; return (Datum) NULL; } } /* * Disconnect from SPI manager and then create the return values datum * (if the input function does a palloc for it this must not be * allocated in the SPI memory context because SPI_finish would free * it). */ if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); plerrcontext.callback = plpython_return_error_callback; plerrcontext.previous = error_context_stack; error_context_stack = &plerrcontext; /* * If the function is declared to return void, the Python return value * must be None. For void-returning functions, we also treat a None * return value as a special "void datum" rather than NULL (as is the * case for non-void-returning functions). */ if (proc->result.out.d.typoid == VOIDOID) { if (plrv != Py_None) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("PL/Python function with return type \"void\" did not return None"))); fcinfo->isnull = false; rv = (Datum) 0; } else if (plrv == Py_None) { fcinfo->isnull = true; if (proc->result.is_rowtype < 1) rv = InputFunctionCall(&proc->result.out.d.typfunc, NULL, proc->result.out.d.typioparam, -1); else /* Tuple as None */ rv = (Datum) NULL; } else if (proc->result.is_rowtype >= 1) { TupleDesc desc; /* make sure it's not an unnamed record */ Assert((proc->result.out.d.typoid == RECORDOID && proc->result.out.d.typmod != -1) || (proc->result.out.d.typoid != RECORDOID && proc->result.out.d.typmod == -1)); desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid, proc->result.out.d.typmod); rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv); fcinfo->isnull = (rv == (Datum) NULL); ReleaseTupleDesc(desc); } else { fcinfo->isnull = false; rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv); } } PG_CATCH(); { Py_XDECREF(plargs); Py_XDECREF(plrv); /* * If there was an error the iterator might have not been exhausted * yet. Set it to NULL so the next invocation of the function will * start the iteration again. */ Py_XDECREF(proc->setof); proc->setof = NULL; PG_RE_THROW(); } PG_END_TRY(); error_context_stack = plerrcontext.previous; Py_XDECREF(plargs); Py_DECREF(plrv); return rv; }