src/backend/tcop/dest.c是如下函数定义的文件,其包含了服务端回送结果所需的函数。

*   INTERFACE ROUTINES
* BeginCommand - initialize the destination at start of command
* CreateDestReceiver - create tuple receiver object for destination
* EndCommand - clean up the destination at end of command
* NullCommand - tell dest that an empty query string was recognized
* ReadyForQuery - tell dest that we are ready for

BeginCommand函数在命令开始时初始化目标。

void BeginCommand(const char *commandTag, CommandDest dest) {
/* Nothing to do at present */
}

CreateDestReceiver函数为发送目的地创建元组接收器对象。以DestRemote为例,其调用​​printtup_create_DR(dest)​​可以返回DestReceiver对象,CreateDestReceiver函数就是一个工厂函数

DestReceiver *CreateDestReceiver(CommandDest dest) {
/* It's ok to cast the constness away as any modification of the none receiver would be a bug (which gets easier to catch this way). 可以将 constness 丢弃,因为对 none 接收器的任何修改都将是一个错误(这样更容易捕获)。 */
switch (dest){
case DestRemote:
case DestRemoteExecute: return printtup_create_DR(dest);
case DestRemoteSimple: return unconstify(DestReceiver *, &printsimpleDR);
case DestNone: return unconstify(DestReceiver *, &donothingDR);
case DestDebug: return unconstify(DestReceiver *, &debugtupDR);
case DestSPI: return unconstify(DestReceiver *, &spi_printtupDR);
case DestTuplestore: return CreateTuplestoreDestReceiver();
case DestIntoRel: return CreateIntoRelDestReceiver(NULL);
case DestCopyOut: return CreateCopyDestReceiver();
case DestSQLFunction: return CreateSQLFunctionDestReceiver();
case DestTransientRel: return CreateTransientRelDestReceiver(InvalidOid);
case DestTupleQueue: return CreateTupleQueueDestReceiver(NULL);
}
/* should never get here */
pg_unreachable();
}

DestReceiver 是特定于目的地的本地状态的基本类型。 在最简单的情况下,没有状态信息,只有执行程序必须调用的函数指针。注意:必须向 receiveSlot 例程传递一个包含 TupleDesc 的槽,该槽与给定给 rStartup 例程的槽相同。 它返回 bool,其中“true”值表示“继续处理”,“false”值表示“提前停止,就像我们已经到达扫描结束一样”。DestReceiver is a base type for destination-specific local state. In the simplest cases, there is no state info, just the function pointers that the executor must call.Note: the receiveSlot routine must be passed a slot containing a TupleDesc identical to the one given to the rStartup routine. It returns bool where a “true” value means “continue processing” and a “false” value means “stop early, just as if we’d reached the end of the scan”.

typedef struct _DestReceiver DestReceiver;
struct _DestReceiver {
bool (*receiveSlot) (TupleTableSlot *slot, DestReceiver *self); /* Called for each tuple to be output: */
void (*rStartup) (DestReceiver *self, int operation, TupleDesc typeinfo); /* Per-executor-run initialization and shutdown: */
void (*rShutdown) (DestReceiver *self);
void (*rDestroy) (DestReceiver *self); /* Destroy the receiver object itself (if dynamically allocated) */
CommandDest mydest; /* CommandDest code for this receiver */
/* Private fields might appear beyond this point... */
};

printtup_create_DR函数

typedef struct {
DestReceiver pub; /* publicly-known function pointers */
Portal portal; /* the Portal we are printing from */
bool sendDescrip; /* send RowDescription at startup? 在一开始是否发送RowDescription */
TupleDesc attrinfo; /* The attr info we are set up for */
int nattrs;
PrinttupAttrInfo *myinfo; /* Cached info about each attr */
StringInfoData buf; /* output buffer (*not* in tmpcontext) */
MemoryContext tmpcontext; /* Memory context for per-row workspace */
} DR_printtup;
DestReceiver *printtup_create_DR(CommandDest dest){
DR_printtup *self = (DR_printtup *) palloc0(sizeof(DR_printtup));
// 初始化DestReceiver通用函数
self->pub.receiveSlot = printtup; /* might get changed later */
self->pub.rStartup = printtup_startup;
self->pub.rShutdown = printtup_shutdown;
self->pub.rDestroy = printtup_destroy;
self->pub.mydest = dest;

/* Send T message automatically if DestRemote, but not if DestRemoteExecute */
self->sendDescrip = (dest == DestRemote);
self->attrinfo = NULL;
self->nattrs = 0;
self->myinfo = NULL;
self->buf.data = NULL;
self->tmpcontext = NULL;
return (DestReceiver *) self;
}

EndCommand函数在命令结束时清理目标。

void EndCommand(const char *commandTag, CommandDest dest) {
switch (dest) {
case DestRemote:
case DestRemoteExecute:
case DestRemoteSimple:
/* We assume the commandTag is plain ASCII and therefore requires no encoding conversion. */
pq_putmessage('C', commandTag, strlen(commandTag) + 1);
break;
case DestNone:
case DestDebug:
case DestSPI:
case DestTuplestore:
case DestIntoRel:
case DestCopyOut:
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
break;
}
}

服务端回送结果的整体流程的一个例子如下所示,其中最为重要的就是调用dest->receiveSlot(slot, dest)函数,如果该函数返回“true”值表示“继续处理”,“false”值表示“提前停止,就像我们已经到达扫描结束一样”。我们看看printtup函数的逻辑是什么样子的。

exec_simple_query
| -- CommandDest dest = whereToSendOutput; // 数据发送的目的地
| -- foreach(parsetree_item, parsetree_list)
| -- commandTag = CreateCommandTag(parsetree->stmt);
| -- BeginCommand(commandTag, dest); // 在命令开始时初始化目标
| -- portal = CreatePortal("", true, true);
| -- PortalDefineQuery(portal, NULL, query_string, commandTag, plantree_list, NULL);
| -- PortalStart(portal, NULL, 0, InvalidSnapshot);
| -- PortalSetResultFormat(portal, 1, &format);
| -- receiver = CreateDestReceiver(dest);
| -- if (dest == DestRemote) SetRemoteDestReceiverParams(receiver, portal);
| -- (void) PortalRun(portal, FETCH_ALL, true, true, receiver, receiver, completionTag);
| -- ExecutorRun
| -- standard_ExecutorRun
| -- sendTuples = (operation == CMD_SELECT || queryDesc->plannedstmt->hasReturning);
| -- if (sendTuples) dest->rStartup(dest, operation, queryDesc->tupDesc);
| -- ExecutePlan(estate, queryDesc->planstate,queryDesc->plannedstmt->parallelModeNeeded,operation,sendTuples,count,direction,dest,execute_once);
| -- if (sendTuples) if (!dest->receiveSlot(slot, dest)) break;
| -- if (sendTuples) dest->rShutdown(dest);
| -- receiver->rDestroy(receiver);
| -- PortalDrop(portal, false);
| -- EndCommand(completionTag, dest);

printtup函数以3.0协议print一个元组。

static bool printtup(TupleTableSlot *slot, DestReceiver *self) {
TupleDesc typeinfo = slot->tts_tupleDescriptor;
DR_printtup *myState = (DR_printtup *) self;
MemoryContext oldcontext;
StringInfo buf = &myState->buf;
int natts = typeinfo->natts;
int i;
/* Set or update my derived attribute info, if needed */
if (myState->attrinfo != typeinfo || myState->nattrs != natts) printtup_prepare_info(myState, typeinfo, natts);
slot_getallattrs(slot); /* Make sure the tuple is fully deconstructed */
oldcontext = MemoryContextSwitchTo(myState->tmpcontext); /* Switch into per-row context so we can recover memory below */
pq_beginmessage_reuse(buf, 'D'); /* Prepare a DataRow message (note buffer is in per-row context) 准备一个DataRow消息*/
pq_sendint16(buf, natts);
for (i = 0; i < natts; ++i) { /* send the attributes of this tuple 发送属性 */
PrinttupAttrInfo *thisState = myState->myinfo + i;
Datum attr = slot->tts_values[i];
if (slot->tts_isnull[i]){
pq_sendint32(buf, -1); // 如果当前per-attribute为null,则发送-1
continue;
}
/* Here we catch undefined bytes in datums that are returned to the client without hitting disk; see comments at the related check in PageAddItem(). This test is most useful for uncompressed, non-external datums, but we're quite likely to see such here when testing new C functions. */
if (thisState->typisvarlena) VALGRIND_CHECK_MEM_IS_DEFINED(DatumGetPointer(attr), VARSIZE_ANY(attr));
if (thisState->format == 0) {
/* Text output */
char *outputstr;
outputstr = OutputFunctionCall(&thisState->finfo, attr);
pq_sendcountedtext(buf, outputstr, strlen(outputstr), false);
}else{
/* Binary output */
bytea *outputbytes;
outputbytes = SendFunctionCall(&thisState->finfo, attr);
pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ);
pq_sendbytes(buf, VARDATA(outputbytes), VARSIZE(outputbytes) - VARHDRSZ);
}
}
pq_endmessage_reuse(buf);
/* Return to caller's context, and flush row's temporary memory */
MemoryContextSwitchTo(oldcontext);
MemoryContextReset(myState->tmpcontext);
return true;
}