/* * compatible_tupdescs: detect whether two tupdescs are physically compatible * * TRUE indicates that a tuple satisfying src_tupdesc can be used directly as * a value for a composite variable using dst_tupdesc. */ static bool compatible_tupdescs(TupleDesc src_tupdesc, TupleDesc dst_tupdesc) { int i; /* Possibly we could allow src_tupdesc to have extra columns? */ if (dst_tupdesc->natts != src_tupdesc->natts) return false; for (i = 0; i < dst_tupdesc->natts; i++) { Form_pg_attribute dattr = TupleDescAttr(dst_tupdesc, i); Form_pg_attribute sattr = TupleDescAttr(src_tupdesc, i); if (dattr->attisdropped != sattr->attisdropped) return false; if (!dattr->attisdropped) { /* Normal columns must match by type and typmod */ if (dattr->atttypid != sattr->atttypid || (dattr->atttypmod >= 0 && dattr->atttypmod != sattr->atttypmod)) return false; } else { /* Dropped columns are OK as long as length/alignment match */ if (dattr->attlen != sattr->attlen || dattr->attalign != sattr->attalign) return false; } } return true; }
/* ---------------- * debugtup - print one tuple for an interactive backend * ---------------- */ bool debugtup(TupleTableSlot *slot, DestReceiver *self) { TupleDesc typeinfo = slot->tts_tupleDescriptor; int natts = typeinfo->natts; int i; Datum attr; char *value; bool isnull; Oid typoutput; bool typisvarlena; for (i = 0; i < natts; ++i) { attr = slot_getattr(slot, i + 1, &isnull); if (isnull) continue; getTypeOutputInfo(TupleDescAttr(typeinfo, i)->atttypid, &typoutput, &typisvarlena); value = OidOutputFunctionCall(typoutput, attr); printatt((unsigned) i + 1, TupleDescAttr(typeinfo, i), value); } printf("\t----\n"); return true; }
/* * Write relation attributes to the stream. */ static void logicalrep_write_attrs(StringInfo out, Relation rel) { TupleDesc desc; int i; uint16 nliveatts = 0; Bitmapset *idattrs = NULL; bool replidentfull; desc = RelationGetDescr(rel); /* send number of live attributes */ for (i = 0; i < desc->natts; i++) { if (TupleDescAttr(desc, i)->attisdropped) continue; nliveatts++; } pq_sendint(out, nliveatts, 2); /* fetch bitmap of REPLICATION IDENTITY attributes */ replidentfull = (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL); if (!replidentfull) idattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_IDENTITY_KEY); /* send the attributes */ for (i = 0; i < desc->natts; i++) { Form_pg_attribute att = TupleDescAttr(desc, i); uint8 flags = 0; if (att->attisdropped) continue; /* REPLICA IDENTITY FULL means all columns are sent as part of key. */ if (replidentfull || bms_is_member(att->attnum - FirstLowInvalidHeapAttributeNumber, idattrs)) flags |= LOGICALREP_IS_REPLICA_IDENTITY; pq_sendbyte(out, flags); /* attribute name */ pq_sendstring(out, NameStr(att->attname)); /* attribute type id */ pq_sendint(out, (int) att->atttypid, sizeof(att->atttypid)); /* attribute mode */ pq_sendint(out, att->atttypmod, sizeof(att->atttypmod)); } bms_free(idattrs); }
/* * Return the missing value of an attribute, or NULL if there isn't one. */ static Datum getmissingattr(TupleDesc tupleDesc, int attnum, bool *isnull) { Form_pg_attribute att; Assert(attnum <= tupleDesc->natts); Assert(attnum > 0); att = TupleDescAttr(tupleDesc, attnum - 1); if (att->atthasmissing) { AttrMissing *attrmiss; Assert(tupleDesc->constr); Assert(tupleDesc->constr->missing); attrmiss = tupleDesc->constr->missing + (attnum - 1); if (attrmiss->am_present) { *isnull = false; return attrmiss->am_value; } } *isnull = true; return PointerGetDatum(NULL); }
/* * Per-heap-tuple callback for IndexBuildHeapScan. * * Note we don't worry about the page range at the end of the table here; it is * present in the build state struct after we're called the last time, but not * inserted into the index. Caller must ensure to do so, if appropriate. */ static void brinbuildCallback(Relation index, HeapTuple htup, Datum *values, bool *isnull, bool tupleIsAlive, void *brstate) { BrinBuildState *state = (BrinBuildState *) brstate; BlockNumber thisblock; int i; thisblock = ItemPointerGetBlockNumber(&htup->t_self); /* * If we're in a block that belongs to a future range, summarize what * we've got and start afresh. Note the scan might have skipped many * pages, if they were devoid of live tuples; make sure to insert index * tuples for those too. */ while (thisblock > state->bs_currRangeStart + state->bs_pagesPerRange - 1) { BRIN_elog((DEBUG2, "brinbuildCallback: completed a range: %u--%u", state->bs_currRangeStart, state->bs_currRangeStart + state->bs_pagesPerRange)); /* create the index tuple and insert it */ form_and_insert_tuple(state); /* set state to correspond to the next range */ state->bs_currRangeStart += state->bs_pagesPerRange; /* re-initialize state for it */ brin_memtuple_initialize(state->bs_dtuple, state->bs_bdesc); } /* Accumulate the current tuple into the running state */ for (i = 0; i < state->bs_bdesc->bd_tupdesc->natts; i++) { FmgrInfo *addValue; BrinValues *col; Form_pg_attribute attr = TupleDescAttr(state->bs_bdesc->bd_tupdesc, i); col = &state->bs_dtuple->bt_columns[i]; addValue = index_getprocinfo(index, i + 1, BRIN_PROCNUM_ADDVALUE); /* * Update dtuple state, if and as necessary. */ FunctionCall4Coll(addValue, attr->attcollation, PointerGetDatum(state->bs_bdesc), PointerGetDatum(col), values[i], isnull[i]); } }
static bool tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc) { int numattrs = tupdesc->natts; int attrno; bool hasoid; ListCell *tlist_item = list_head(tlist); /* Check the tlist attributes */ for (attrno = 1; attrno <= numattrs; attrno++) { Form_pg_attribute att_tup = TupleDescAttr(tupdesc, attrno - 1); Var *var; if (tlist_item == NULL) return false; /* tlist too short */ var = (Var *) ((TargetEntry *) lfirst(tlist_item))->expr; if (!var || !IsA(var, Var)) return false; /* tlist item not a Var */ /* if these Asserts fail, planner messed up */ Assert(var->varno == varno); Assert(var->varlevelsup == 0); if (var->varattno != attrno) return false; /* out of order */ if (att_tup->attisdropped) return false; /* table contains dropped columns */ /* * Note: usually the Var's type should match the tupdesc exactly, but * in situations involving unions of columns that have different * typmods, the Var may have come from above the union and hence have * typmod -1. This is a legitimate situation since the Var still * describes the column, just not as exactly as the tupdesc does. We * could change the planner to prevent it, but it'd then insert * projection steps just to convert from specific typmod to typmod -1, * which is pretty silly. */ if (var->vartype != att_tup->atttypid || (var->vartypmod != att_tup->atttypmod && var->vartypmod != -1)) return false; /* type mismatch */ tlist_item = lnext(tlist_item); } if (tlist_item) return false; /* tlist too long */ /* * If the plan context requires a particular hasoid setting, then that has * to match, too. */ if (ExecContextForcesOids(ps, &hasoid) && hasoid != tupdesc->tdhasoid) return false; return true; }
void luaP_pushdesctable (lua_State *L, TupleDesc desc) { int i; lua_newtable(L); for (i = 0; i < desc->natts; i++) { lua_pushstring(L, NameStr(TupleDescAttr(desc, i)->attname)); lua_pushinteger(L, i); lua_rawset(L, -3); /* t[att] = i */ } }
/* Copied from src/backend/optimizer/util/plancat.c, not exported. * * Build a targetlist representing the columns of the specified index. * Each column is represented by a Var for the corresponding base-relation * column, or an expression in base-relation Vars, as appropriate. * * There are never any dropped columns in indexes, so unlike * build_physical_tlist, we need no failure case. */ List * build_index_tlist(PlannerInfo *root, IndexOptInfo *index, Relation heapRelation) { List *tlist = NIL; Index varno = index->rel->relid; ListCell *indexpr_item; int i; indexpr_item = list_head(index->indexprs); for (i = 0; i < index->ncolumns; i++) { int indexkey = index->indexkeys[i]; Expr *indexvar; if (indexkey != 0) { /* simple column */ Form_pg_attribute att_tup; if (indexkey < 0) att_tup = SystemAttributeDefinition(indexkey, heapRelation->rd_rel->relhasoids); else #if PG_VERSION_NUM >= 110000 att_tup = TupleDescAttr(heapRelation->rd_att, indexkey - 1); #else att_tup = heapRelation->rd_att->attrs[indexkey - 1]; #endif indexvar = (Expr *) makeVar(varno, indexkey, att_tup->atttypid, att_tup->atttypmod, att_tup->attcollation, 0); } else { /* expression column */ if (indexpr_item == NULL) elog(ERROR, "wrong number of index expressions"); indexvar = (Expr *) lfirst(indexpr_item); indexpr_item = lnext(indexpr_item); } tlist = lappend(tlist, makeTargetEntry(indexvar, i + 1, NULL, false)); } if (indexpr_item != NULL) elog(ERROR, "wrong number of index expressions"); return tlist; }
/* * Cache and return the procedure for the given strategy. * * Note: this function mirrors inclusion_get_strategy_procinfo; see notes * there. If changes are made here, see that function too. */ static FmgrInfo * minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum) { MinmaxOpaque *opaque; Assert(strategynum >= 1 && strategynum <= BTMaxStrategyNumber); opaque = (MinmaxOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; /* * We cache the procedures for the previous subtype in the opaque struct, * to avoid repetitive syscache lookups. If the subtype changed, * invalidate all the cached entries. */ if (opaque->cached_subtype != subtype) { uint16 i; for (i = 1; i <= BTMaxStrategyNumber; i++) opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid; opaque->cached_subtype = subtype; } if (opaque->strategy_procinfos[strategynum - 1].fn_oid == InvalidOid) { Form_pg_attribute attr; HeapTuple tuple; Oid opfamily, oprid; bool isNull; opfamily = bdesc->bd_index->rd_opfamily[attno - 1]; attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily), ObjectIdGetDatum(attr->atttypid), ObjectIdGetDatum(subtype), Int16GetDatum(strategynum)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", strategynum, attr->atttypid, subtype, opfamily); oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, tuple, Anum_pg_amop_amopopr, &isNull)); ReleaseSysCache(tuple); Assert(!isNull && RegProcedureIsValid(oprid)); fmgr_info_cxt(get_opcode(oprid), &opaque->strategy_procinfos[strategynum - 1], bdesc->bd_context); } return &opaque->strategy_procinfos[strategynum - 1]; }
/* * Build a BrinDesc used to create or scan a BRIN index */ BrinDesc * brin_build_desc(Relation rel) { BrinOpcInfo **opcinfo; BrinDesc *bdesc; TupleDesc tupdesc; int totalstored = 0; int keyno; long totalsize; MemoryContext cxt; MemoryContext oldcxt; cxt = AllocSetContextCreate(CurrentMemoryContext, "brin desc cxt", ALLOCSET_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(cxt); tupdesc = RelationGetDescr(rel); /* * Obtain BrinOpcInfo for each indexed column. While at it, accumulate * the number of columns stored, since the number is opclass-defined. */ opcinfo = (BrinOpcInfo **) palloc(sizeof(BrinOpcInfo *) * tupdesc->natts); for (keyno = 0; keyno < tupdesc->natts; keyno++) { FmgrInfo *opcInfoFn; Form_pg_attribute attr = TupleDescAttr(tupdesc, keyno); opcInfoFn = index_getprocinfo(rel, keyno + 1, BRIN_PROCNUM_OPCINFO); opcinfo[keyno] = (BrinOpcInfo *) DatumGetPointer(FunctionCall1(opcInfoFn, attr->atttypid)); totalstored += opcinfo[keyno]->oi_nstored; } /* Allocate our result struct and fill it in */ totalsize = offsetof(BrinDesc, bd_info) + sizeof(BrinOpcInfo *) * tupdesc->natts; bdesc = palloc(totalsize); bdesc->bd_context = cxt; bdesc->bd_index = rel; bdesc->bd_tupdesc = tupdesc; bdesc->bd_disktdesc = NULL; /* generated lazily */ bdesc->bd_totalstored = totalstored; for (keyno = 0; keyno < tupdesc->natts; keyno++) bdesc->bd_info[keyno] = opcinfo[keyno]; pfree(opcinfo); MemoryContextSwitchTo(oldcxt); return bdesc; }
/* * Check if spi sql tupdesc and return tupdesc are compatible */ static void compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc) { Oid ret_atttypid; Oid sql_atttypid; int32 ret_atttypmod; int32 sql_atttypmod; /* * Result must have at least 2 columns. */ if (sql_tupdesc->natts < 2) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("invalid return type"), errdetail("Query must return at least two columns."))); /* * These columns must match the result type indicated by the calling * query. */ ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid; sql_atttypid = TupleDescAttr(sql_tupdesc, 0)->atttypid; ret_atttypmod = TupleDescAttr(ret_tupdesc, 0)->atttypmod; sql_atttypmod = TupleDescAttr(sql_tupdesc, 0)->atttypmod; if (ret_atttypid != sql_atttypid || (ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("invalid return type"), errdetail("SQL key field type %s does " \ "not match return key field type %s.", format_type_with_typemod(ret_atttypid, ret_atttypmod), format_type_with_typemod(sql_atttypid, sql_atttypmod)))); ret_atttypid = TupleDescAttr(ret_tupdesc, 1)->atttypid; sql_atttypid = TupleDescAttr(sql_tupdesc, 1)->atttypid; ret_atttypmod = TupleDescAttr(ret_tupdesc, 1)->atttypmod; sql_atttypmod = TupleDescAttr(sql_tupdesc, 1)->atttypmod; if (ret_atttypid != sql_atttypid || (ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("invalid return type"), errdetail("SQL parent key field type %s does " \ "not match return parent key field type %s.", format_type_with_typemod(ret_atttypid, ret_atttypmod), format_type_with_typemod(sql_atttypid, sql_atttypmod)))); /* OK, the two tupdescs are compatible for our purposes */ }
/* * Check if two tupdescs match in type of attributes */ static bool compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc) { int i; Form_pg_attribute ret_attr; Oid ret_atttypid; Form_pg_attribute sql_attr; Oid sql_atttypid; if (ret_tupdesc->natts < 2 || sql_tupdesc->natts < 3) return false; /* check the rowid types match */ ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid; sql_atttypid = TupleDescAttr(sql_tupdesc, 0)->atttypid; if (ret_atttypid != sql_atttypid) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("invalid return type"), errdetail("SQL rowid datatype does not match " \ "return rowid datatype."))); /* * - attribute [1] of the sql tuple is the category; no need to check it - * attribute [2] of the sql tuple should match attributes [1] to [natts] * of the return tuple */ sql_attr = TupleDescAttr(sql_tupdesc, 2); for (i = 1; i < ret_tupdesc->natts; i++) { ret_attr = TupleDescAttr(ret_tupdesc, i); if (ret_attr->atttypid != sql_attr->atttypid) return false; } /* OK, the two tupdescs are compatible for our purposes */ return true; }
/* ---------------- * debugStartup - prepare to print tuples for an interactive backend * ---------------- */ void debugStartup(DestReceiver *self, int operation, TupleDesc typeinfo) { int natts = typeinfo->natts; int i; /* * show the return type of the tuples */ for (i = 0; i < natts; ++i) printatt((unsigned) i + 1, TupleDescAttr(typeinfo, i), NULL); printf("\t----\n"); }
/* * Receive a tuple from the executor and store it in the tuplestore. * This is for the case where we have to detoast any toasted values. */ static bool tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self) { TStoreState *myState = (TStoreState *) self; TupleDesc typeinfo = slot->tts_tupleDescriptor; int natts = typeinfo->natts; int nfree; int i; MemoryContext oldcxt; /* Make sure the tuple is fully deconstructed */ slot_getallattrs(slot); /* * Fetch back any out-of-line datums. We build the new datums array in * myState->outvalues[] (but we can re-use the slot's isnull array). Also, * remember the fetched values to free afterwards. */ nfree = 0; for (i = 0; i < natts; i++) { Datum val = slot->tts_values[i]; Form_pg_attribute attr = TupleDescAttr(typeinfo, i); if (!attr->attisdropped && attr->attlen == -1 && !slot->tts_isnull[i]) { if (VARATT_IS_EXTERNAL(DatumGetPointer(val))) { val = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(val))); myState->tofree[nfree++] = val; } } myState->outvalues[i] = val; } /* * Push the modified tuple into the tuplestore. */ oldcxt = MemoryContextSwitchTo(myState->cxt); tuplestore_putvalues(myState->tstore, typeinfo, myState->outvalues, slot->tts_isnull); MemoryContextSwitchTo(oldcxt); /* And release any temporary detoasted values */ for (i = 0; i < nfree; i++) pfree(DatumGetPointer(myState->tofree[i])); return true; }
/* * Executes default values for columns for which we can't map to remote * relation columns. * * This allows us to support tables which have more columns on the downstream * than on the upstream. */ static void slot_fill_defaults(LogicalRepRelMapEntry *rel, EState *estate, TupleTableSlot *slot) { TupleDesc desc = RelationGetDescr(rel->localrel); int num_phys_attrs = desc->natts; int i; int attnum, num_defaults = 0; int *defmap; ExprState **defexprs; ExprContext *econtext; econtext = GetPerTupleExprContext(estate); /* We got all the data via replication, no need to evaluate anything. */ if (num_phys_attrs == rel->remoterel.natts) return; defmap = (int *) palloc(num_phys_attrs * sizeof(int)); defexprs = (ExprState **) palloc(num_phys_attrs * sizeof(ExprState *)); for (attnum = 0; attnum < num_phys_attrs; attnum++) { Expr *defexpr; if (TupleDescAttr(desc, attnum)->attisdropped) continue; if (rel->attrmap[attnum] >= 0) continue; defexpr = (Expr *) build_column_default(rel->localrel, attnum + 1); if (defexpr != NULL) { /* Run the expression through planner */ defexpr = expression_planner(defexpr); /* Initialize executable expression in copycontext */ defexprs[num_defaults] = ExecInitExpr(defexpr, NULL); defmap[num_defaults] = attnum; num_defaults++; } } for (i = 0; i < num_defaults; i++) slot->tts_values[defmap[i]] = ExecEvalExpr(defexprs[i], econtext, &slot->tts_isnull[defmap[i]]); }
/* * heap_compute_data_size * Determine size of the data area of a tuple to be constructed */ Size heap_compute_data_size(TupleDesc tupleDesc, Datum *values, bool *isnull) { Size data_length = 0; int i; int numberOfAttributes = tupleDesc->natts; for (i = 0; i < numberOfAttributes; i++) { Datum val; Form_pg_attribute atti; if (isnull[i]) continue; val = values[i]; atti = TupleDescAttr(tupleDesc, i); if (ATT_IS_PACKABLE(atti) && VARATT_CAN_MAKE_SHORT(DatumGetPointer(val))) { /* * we're anticipating converting to a short varlena header, so * adjust length and don't count any alignment */ data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val)); } else if (atti->attlen == -1 && VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val))) { /* * we want to flatten the expanded value so that the constructed * tuple doesn't depend on it */ data_length = att_align_nominal(data_length, atti->attalign); data_length += EOH_get_flat_size(DatumGetEOHP(val)); } else { data_length = att_align_datum(data_length, atti->attalign, atti->attlen, val); data_length = att_addlength_datum(data_length, atti->attlen, val); } } return data_length; }
/* * Transform a tuple into a Python dict object. */ static PyObject * PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc) { PyObject *volatile dict; /* Simple sanity check that desc matches */ Assert(desc->natts == arg->u.tuple.natts); dict = PyDict_New(); if (dict == NULL) return NULL; PG_TRY(); { int i; for (i = 0; i < arg->u.tuple.natts; i++) { PLyDatumToOb *att = &arg->u.tuple.atts[i]; Form_pg_attribute attr = TupleDescAttr(desc, i); char *key; Datum vattr; bool is_null; PyObject *value; if (attr->attisdropped) continue; key = NameStr(attr->attname); vattr = heap_getattr(tuple, (i + 1), desc, &is_null); if (is_null) PyDict_SetItemString(dict, key, Py_None); else { value = att->func(att, vattr); PyDict_SetItemString(dict, key, value); Py_DECREF(value); } } } PG_CATCH(); { Py_DECREF(dict); PG_RE_THROW(); } PG_END_TRY(); return dict; }
/* * Check to see whether the table needs a TOAST table. It does only if * (1) there are any toastable attributes, and (2) the maximum length * of a tuple could exceed TOAST_TUPLE_THRESHOLD. (We don't want to * create a toast table for something like "f1 varchar(20)".) * No need to create a TOAST table for partitioned tables. */ static bool needs_toast_table(Relation rel) { int32 data_length = 0; bool maxlength_unknown = false; bool has_toastable_attrs = false; TupleDesc tupdesc; int32 tuple_length; int i; if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) return false; tupdesc = rel->rd_att; for (i = 0; i < tupdesc->natts; i++) { Form_pg_attribute att = TupleDescAttr(tupdesc, i); if (att->attisdropped) continue; data_length = att_align_nominal(data_length, att->attalign); if (att->attlen > 0) { /* Fixed-length types are never toastable */ data_length += att->attlen; } else { int32 maxlen = type_maximum_size(att->atttypid, att->atttypmod); if (maxlen < 0) maxlength_unknown = true; else data_length += maxlen; if (att->attstorage != 'p') has_toastable_attrs = true; } } if (!has_toastable_attrs) return false; /* nothing to toast? */ if (maxlength_unknown) return true; /* any unlimited-length attrs? */ tuple_length = MAXALIGN(SizeofHeapTupleHeader + BITMAPLEN(tupdesc->natts)) + MAXALIGN(data_length); return (tuple_length > TOAST_TUPLE_THRESHOLD); }
/* * This is basically the same as datumCopy(), but extended to count * palloc'd space in accum->allocatedMemory. */ static Datum getDatumCopy(BuildAccumulator *accum, OffsetNumber attnum, Datum value) { Form_pg_attribute att; Datum res; att = TupleDescAttr(accum->ginstate->origTupdesc, attnum - 1); if (att->attbyval) res = value; else { res = datumCopy(value, false, att->attlen); accum->allocatedMemory += GetMemoryChunkSpace(DatumGetPointer(res)); } return res; }
/* * Build result tuple from binary or CString values. * * Based on BuildTupleFromCStrings. */ HeapTuple plproxy_recv_composite(ProxyComposite *meta, char **values, int *lengths, int *fmts) { TupleDesc tupdesc = meta->tupdesc; int natts = tupdesc->natts; Datum *dvalues; bool *nulls; int i; HeapTuple tuple; dvalues = (Datum *) palloc(natts * sizeof(Datum)); nulls = (bool *) palloc(natts * sizeof(bool)); /* Call the recv function for each attribute */ for (i = 0; i < natts; i++) { if (TupleDescAttr(tupdesc, i)->attisdropped) { dvalues[i] = (Datum)NULL; nulls[i] = true; continue; } dvalues[i] = plproxy_recv_type(meta->type_list[i], values[i], lengths[i], fmts[i]); nulls[i] = (values[i] == NULL); } /* Form a tuple */ tuple = heap_form_tuple(tupdesc, dvalues, nulls); /* * Release locally palloc'd space. */ for (i = 0; i < natts; i++) { if (nulls[i]) continue; if (meta->type_list[i]->by_value) continue; pfree(DatumGetPointer(dvalues[i])); } pfree(dvalues); pfree(nulls); return tuple; }
/* * Get the lookup info that printtup() needs */ static void printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs) { int16 *formats = myState->portal->formats; int i; /* get rid of any old data */ if (myState->myinfo) pfree(myState->myinfo); myState->myinfo = NULL; myState->attrinfo = typeinfo; myState->nattrs = numAttrs; if (numAttrs <= 0) return; myState->myinfo = (PrinttupAttrInfo *) palloc0(numAttrs * sizeof(PrinttupAttrInfo)); for (i = 0; i < numAttrs; i++) { PrinttupAttrInfo *thisState = myState->myinfo + i; int16 format = (formats ? formats[i] : 0); Form_pg_attribute attr = TupleDescAttr(typeinfo, i); thisState->format = format; if (format == 0) { getTypeOutputInfo(attr->atttypid, &thisState->typoutput, &thisState->typisvarlena); fmgr_info(thisState->typoutput, &thisState->finfo); } else if (format == 1) { getTypeBinaryOutputInfo(attr->atttypid, &thisState->typsend, &thisState->typisvarlena); fmgr_info(thisState->typsend, &thisState->finfo); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unsupported format code: %d", format))); } }
/* * Compare the tuple and slot and check if they have equal values. * * We use binary datum comparison which might return false negatives but * that's the best we can do here as there may be multiple notions of * equality for the data types and table columns don't specify which one * to use. */ static bool tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot) { Datum values[MaxTupleAttributeNumber]; bool isnull[MaxTupleAttributeNumber]; int attrnum; heap_deform_tuple(tup, desc, values, isnull); /* Check equality of the attributes. */ for (attrnum = 0; attrnum < desc->natts; attrnum++) { Form_pg_attribute att; TypeCacheEntry *typentry; /* * If one value is NULL and other is not, then they are certainly not * equal */ if (isnull[attrnum] != slot->tts_isnull[attrnum]) return false; /* * If both are NULL, they can be considered equal. */ if (isnull[attrnum]) continue; att = TupleDescAttr(desc, attrnum); typentry = lookup_type_cache(att->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(att->atttypid)))); if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo, values[attrnum], slot->tts_values[attrnum]))) return false; } return true; }
/* * heap_fill_tuple * Load data portion of a tuple from values/isnull arrays * * We also fill the null bitmap (if any) and set the infomask bits * that reflect the tuple's data contents. * * NOTE: it is now REQUIRED that the caller have pre-zeroed the data area. */ void heap_fill_tuple(TupleDesc tupleDesc, Datum *values, bool *isnull, char *data, Size data_size, uint16 *infomask, bits8 *bit) { bits8 *bitP; int bitmask; int i; int numberOfAttributes = tupleDesc->natts; #ifdef USE_ASSERT_CHECKING char *start = data; #endif if (bit != NULL) { bitP = &bit[-1]; bitmask = HIGHBIT; } else { /* just to keep compiler quiet */ bitP = NULL; bitmask = 0; } *infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTERNAL); for (i = 0; i < numberOfAttributes; i++) { Form_pg_attribute attr = TupleDescAttr(tupleDesc, i); fill_val(attr, bitP ? &bitP : NULL, &bitmask, &data, infomask, values ? values[i] : PointerGetDatum(NULL), isnull ? isnull[i] : true); } Assert((data - start) == data_size); }
/* * Prepare to receive tuples from executor. */ static void tstoreStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo) { TStoreState *myState = (TStoreState *) self; bool needtoast = false; int natts = typeinfo->natts; int i; /* Check if any columns require detoast work */ if (myState->detoast) { for (i = 0; i < natts; i++) { Form_pg_attribute attr = TupleDescAttr(typeinfo, i); if (attr->attisdropped) continue; if (attr->attlen == -1) { needtoast = true; break; } } } /* Set up appropriate callback */ if (needtoast) { myState->pub.receiveSlot = tstoreReceiveSlot_detoast; /* Create workspace */ myState->outvalues = (Datum *) MemoryContextAlloc(myState->cxt, natts * sizeof(Datum)); myState->tofree = (Datum *) MemoryContextAlloc(myState->cxt, natts * sizeof(Datum)); } else { myState->pub.receiveSlot = tstoreReceiveSlot_notoast; myState->outvalues = NULL; myState->tofree = NULL; } }
/* * Initialize, or re-initialize, per-column output info for a composite type. * * This is separate from PLy_output_setup_func() because in cases involving * anonymous record types, we need to be passed the tupdesc explicitly. * It's caller's responsibility that the tupdesc has adequate lifespan * in such cases. If the tupdesc is for a named composite or registered * record type, it does not need to be long-lived. */ void PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc) { int i; /* We should be working on a previously-set-up struct */ Assert(arg->func == PLyObject_ToComposite); /* Save pointer to tupdesc, but only if this is an anonymous record type */ if (arg->typoid == RECORDOID && arg->typmod < 0) arg->u.tuple.recdesc = desc; /* (Re)allocate atts array as needed */ if (arg->u.tuple.natts != desc->natts) { if (arg->u.tuple.atts) pfree(arg->u.tuple.atts); arg->u.tuple.natts = desc->natts; arg->u.tuple.atts = (PLyObToDatum *) MemoryContextAllocZero(arg->mcxt, desc->natts * sizeof(PLyObToDatum)); } /* Fill the atts entries, except for dropped columns */ for (i = 0; i < desc->natts; i++) { Form_pg_attribute attr = TupleDescAttr(desc, i); PLyObToDatum *att = &arg->u.tuple.atts[i]; if (attr->attisdropped) continue; if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod) continue; /* already set up this entry */ PLy_output_setup_func(att, arg->mcxt, attr->atttypid, attr->atttypmod, proc); } }
/* ---------------- * heap_attisnull - returns true iff tuple attribute is not present * ---------------- */ bool heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc) { /* * We allow a NULL tupledesc for relations not expected to have missing * values, such as catalog relations and indexes. */ Assert(!tupleDesc || attnum <= tupleDesc->natts); if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data)) { if (tupleDesc && TupleDescAttr(tupleDesc, attnum - 1)->atthasmissing) return false; else return true; } if (attnum > 0) { if (HeapTupleNoNulls(tup)) return false; return att_isnull(attnum - 1, tup->t_data->t_bits); } switch (attnum) { case TableOidAttributeNumber: case SelfItemPointerAttributeNumber: case MinTransactionIdAttributeNumber: case MinCommandIdAttributeNumber: case MaxTransactionIdAttributeNumber: case MaxCommandIdAttributeNumber: /* these are never null */ break; default: elog(ERROR, "invalid attnum: %d", attnum); } return false; }
static void luaP_pushtuple_cmn (lua_State *L, HeapTuple tuple, int readonly, RTupDesc* rtupdesc) { luaP_Tuple *t; TupleDesc tupleDesc; int i, n; BEGINLUA; tupleDesc = rtupdesc->tupdesc; n = tupleDesc->natts; t = lua_newuserdata(L, sizeof(luaP_Tuple) + n * (sizeof(Datum) + sizeof(bool))); t->value = (Datum *) (t + 1); t->null = (bool *) (t->value + n); t->rtupdesc = rtupdesc_ref(rtupdesc); for (i = 0; i < n; i++) { bool isnull; t->value[i] = heap_getattr(tuple, TupleDescAttr(tupleDesc, i)->attnum, tupleDesc, &isnull); t->null[i] = isnull; } if (readonly) { t->changed = -1; } else { t->changed = 0; } t->tupdesc = 0; t->relid = 0; t->tuple = tuple; luaP_getfield(L, PLLUA_TUPLEMT); lua_setmetatable(L, -2); ENDLUAV(1); }
/* * Send description for each column when using v2 protocol */ static void SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats) { int natts = typeinfo->natts; int i; for (i = 0; i < natts; ++i) { Form_pg_attribute att = TupleDescAttr(typeinfo, i); Oid atttypid = att->atttypid; int32 atttypmod = att->atttypmod; /* If column is a domain, send the base type and typmod instead */ atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod); pq_sendstring(buf, NameStr(att->attname)); /* column ID only info appears in protocol 3.0 and up */ pq_sendint32(buf, atttypid); pq_sendint16(buf, att->attlen); pq_sendint32(buf, atttypmod); /* format info only appears in protocol 3.0 and up */ } }
/* * Verify lvalue It doesn't repeat a checks that are done. Checks a subscript * expressions, verify a validity of record's fields. */ void plpgsql_check_target(PLpgSQL_checkstate *cstate, int varno, Oid *expected_typoid, int *expected_typmod) { PLpgSQL_datum *target = cstate->estate->datums[varno]; plpgsql_check_record_variable_usage(cstate, varno, true); switch (target->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) target; PLpgSQL_type *tp = var->datatype; if (expected_typoid != NULL) *expected_typoid = tp->typoid; if (expected_typmod != NULL) *expected_typmod = tp->atttypmod; } break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) target; #if PG_VERSION_NUM >= 110000 if (rec->rectypeid != RECORDOID) { if (expected_typoid != NULL) *expected_typoid = rec->rectypeid; if (expected_typmod != NULL) *expected_typmod = -1; } else #endif if (recvar_tupdesc(rec) != NULL) { if (expected_typoid != NULL) *expected_typoid = recvar_tupdesc(rec)->tdtypeid; if (expected_typmod != NULL) *expected_typmod = recvar_tupdesc(rec)->tdtypmod; } else { if (expected_typoid != NULL) *expected_typoid = RECORDOID; if (expected_typmod != NULL) *expected_typmod = -1; } } break; case PLPGSQL_DTYPE_ROW: { PLpgSQL_row *row = (PLpgSQL_row *) target; if (row->rowtupdesc != NULL) { if (expected_typoid != NULL) *expected_typoid = row->rowtupdesc->tdtypeid; if (expected_typmod != NULL) *expected_typmod = row->rowtupdesc->tdtypmod; } else { if (expected_typoid != NULL) *expected_typoid = RECORDOID; if (expected_typmod != NULL) *expected_typmod = -1; } plpgsql_check_row_or_rec(cstate, row, NULL); } break; case PLPGSQL_DTYPE_RECFIELD: { PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target; PLpgSQL_rec *rec; int fno; rec = (PLpgSQL_rec *) (cstate->estate->datums[recfield->recparentno]); /* * Check that there is already a tuple in the record. We need * that because records don't have any predefined field * structure. */ if (!HeapTupleIsValid(recvar_tuple(rec))) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("record \"%s\" is not assigned to tuple structure", rec->refname))); /* * Get the number of the records field to change and the * number of attributes in the tuple. Note: disallow system * column names because the code below won't cope. */ fno = SPI_fnumber(recvar_tupdesc(rec), recfield->fieldname); if (fno <= 0) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("record \"%s\" has no field \"%s\"", rec->refname, recfield->fieldname))); if (expected_typoid) *expected_typoid = SPI_gettypeid(recvar_tupdesc(rec), fno); if (expected_typmod) *expected_typmod = TupleDescAttr(recvar_tupdesc(rec), fno - 1)->atttypmod; } break; case PLPGSQL_DTYPE_ARRAYELEM: { /* * Target is an element of an array */ int nsubscripts; Oid arrayelemtypeid; Oid arraytypeid; /* * To handle constructs like x[1][2] := something, we have to * be prepared to deal with a chain of arrayelem datums. Chase * back to find the base array datum, and save the subscript * expressions as we go. (We are scanning right to left here, * but want to evaluate the subscripts left-to-right to * minimize surprises.) */ nsubscripts = 0; do { PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target; if (nsubscripts++ >= MAXDIM) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", nsubscripts + 1, MAXDIM))); plpgsql_check_expr(cstate, arrayelem->subscript); target = cstate->estate->datums[arrayelem->arrayparentno]; } while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM); /* * If target is domain over array, reduce to base type */ #if PG_VERSION_NUM >= 90600 arraytypeid = plpgsql_exec_get_datum_type(cstate->estate, target); #else arraytypeid = exec_get_datum_type(cstate->estate, target); #endif arraytypeid = getBaseType(arraytypeid); arrayelemtypeid = get_element_type(arraytypeid); if (!OidIsValid(arrayelemtypeid)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("subscripted object is not an array"))); if (expected_typoid) *expected_typoid = arrayelemtypeid; if (expected_typmod) *expected_typmod = ((PLpgSQL_var *) target)->datatype->atttypmod; plpgsql_check_record_variable_usage(cstate, target->dno, true); } break; default: ; /* nope */ } }
/* * is_null is true, when we assign NULL expression and type should not be checked. */ void plpgsql_check_recval_assign_tupdesc(PLpgSQL_checkstate *cstate, PLpgSQL_rec *rec, TupleDesc tupdesc, bool is_null) { #if PG_VERSION_NUM >= 110000 PLpgSQL_execstate *estate = cstate->estate; ExpandedRecordHeader *newerh; MemoryContext mcontext; TupleDesc var_tupdesc; Datum *newvalues; bool *newnulls; char *chunk; int vtd_natts; int i; mcontext = get_eval_mcontext(estate); plpgsql_check_recval_release(rec); /* * code is reduced version of make_expanded_record_for_rec */ if (rec->rectypeid != RECORDOID) { newerh = make_expanded_record_from_typeid(rec->rectypeid, -1, mcontext); } else { if (!tupdesc) return; newerh = make_expanded_record_from_tupdesc(tupdesc, mcontext); } /* * code is reduced version of exec_move_row_from_field */ var_tupdesc = expanded_record_get_tupdesc(newerh); vtd_natts = var_tupdesc->natts; if (!is_null && tupdesc != NULL && !compatible_tupdescs(var_tupdesc, tupdesc)) { int i = 0; int j = 0; int target_nfields = 0; int src_nfields = 0; bool src_field_is_valid = false; bool target_field_is_valid = false; Form_pg_attribute sattr = NULL; Form_pg_attribute tattr = NULL; while (i < var_tupdesc->natts || j < tupdesc->natts) { if (!target_field_is_valid && i < var_tupdesc->natts) { tattr = TupleDescAttr(var_tupdesc, i); if (tattr->attisdropped) { i += 1; continue; } target_field_is_valid = true; target_nfields += 1; } if (!src_field_is_valid && j < tupdesc->natts) { sattr = TupleDescAttr(tupdesc, j); if (sattr->attisdropped) { j += 1; continue; } src_field_is_valid = true; src_nfields += 1; } if (src_field_is_valid && target_field_is_valid) { plpgsql_check_assign_to_target_type(cstate, tattr->atttypid, tattr->atttypmod, sattr->atttypid, false); /* try to search next tuple of fields */ src_field_is_valid = false; target_field_is_valid = false; i += 1; j += 1; } else break; } if (src_nfields < target_nfields) plpgsql_check_put_error(cstate, 0, 0, "too few attributes for composite variable", NULL, NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); else if (src_nfields > target_nfields) plpgsql_check_put_error(cstate, 0, 0, "too many attributes for composite variable", NULL, NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); } chunk = eval_mcontext_alloc(estate, vtd_natts * (sizeof(Datum) + sizeof(bool))); newvalues = (Datum *) chunk; newnulls = (bool *) (chunk + vtd_natts * sizeof(Datum)); for (i = 0; i < vtd_natts; i++) { newvalues[i] = (Datum) 0; newnulls[i] = true; } expanded_record_set_fields(newerh, newvalues, newnulls, true); TransferExpandedRecord(newerh, estate->datum_context); rec->erh = newerh; #else bool *nulls; HeapTuple tup; plpgsql_check_recval_release(rec); if (!tupdesc) return; /* initialize rec by NULLs */ nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); memset(nulls, true, tupdesc->natts * sizeof(bool)); rec->tupdesc = CreateTupleDescCopy(tupdesc); rec->freetupdesc = true; tup = heap_form_tuple(tupdesc, NULL, nulls); if (HeapTupleIsValid(tup)) { rec->tup = tup; rec->freetup = true; } else elog(ERROR, "cannot to build valid composite value"); #endif }