#include "postgres.h"
#include "fmgr.h"
#include <string.h>

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

#ifndef SET_VARSIZE
#define SET_VARSIZE(v,l) (VARATT_SIZEP(v) = (l))
#endif

Datum hello( PG_FUNCTION_ARGS );

PG_FUNCTION_INFO_V1( hello );
Datum
hello( PG_FUNCTION_ARGS )
{
// variable declarations
char greet[] = "Hello, ";
text *towhom;
int greetlen;
int towhomlen;
text *greeting;

// Get arguments. If we declare our function as STRICT, then
// this check is superfluous.
if( PG_ARGISNULL(0) ) {
PG_RETURN_NULL();
}
towhom = PG_GETARG_TEXT_P(0);

// Calculate string sizes.
greetlen = strlen(greet);


/*
struct varlena
{
int32 vl_len;
char vl_dat[1];
};
*/
// VARSIZE 宏告诉我们"towhom"文本的总大小。 VARHDRSZ 宏是一个常量,它表示 varlena 结构的vl_len成员消耗的开销。
// 因此, VARSIZE (towhom)-VARHDRSZ为我们提供了文本字符串中八位位组的数量。
towhomlen = VARSIZE(towhom) - VARHDRSZ;

// Allocate memory and set data structure size.
greeting = (text *)palloc( greetlen + towhomlen );
// 我们使用 VARATT_SIZEP 宏将“greeting”文本的vl_len成员设置为等于要存储的文本字符串的大小。
VARATT_SIZEP( greeting ) = greetlen + towhomlen + VARHDRSZ;

// Construct greeting string.
// varlena 数据结构消除了对字符串定界符结尾的需要。 VARDATA 宏返回一个指向“ greeting”字符串的数据成员的指针。
strncpy( VARDATA(greeting), greet, greetlen );
strncpy( VARDATA(greeting) + greetlen,
VARDATA(towhom),
towhomlen );

PG_RETURN_TEXT_P( greeting );
}


CREATE OR REPLACE FUNCTION
hello( TEXT )
RETURNS
TEXT
AS
'example.so', 'hello'
LANGUAGE
C
STRICT
IMMUTABLE;

Open a database with the PostgreSQL interactive command line editor 'psql' as a user with permission to create new functions (i.e. superuser). Create your new 'hello' function by loading the example.sql code above. For example:

prompt> \i example.sql
CREATE FUNCTION

=> CREATE TEMP TABLE test( name ) AS
-> VALUES ('Xavier'), ('Yari'), ('Zack');

=> SELECT hello( name ) FROM test;
hello 
--------------
Hello, Xavier
Hello, Yari
Hello, Zack

//


#include "postgres.h"
#include "fmgr.h"
#include "utils/date.h"
#include "utils/nabstime.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1 (dateserial);

Datum dateserial (PG_FUNCTION_ARGS)
{
int32 year = PG_GETARG_INT32(0);
int32 month = PG_GETARG_INT32(1);
int32 day = PG_GETARG_INT32(2);

DateADT d = date2j (year, month, day) - POSTGRES_EPOCH_JDATE;
PG_RETURN_DATEADT(d);
}

CREATE FUNCTION datefunc(int, int, int) RETURNS date AS '/usr/local/postgresql/share/extension/datefunc.so', 'dateserial' LANGUAGE C STRICT IMMUTABLE;


#include "postgres.h"
#include "fmgr.h"
#include <string.h>
#include "utils/geo_decls.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

#ifndef SET_VARSIZE
#define SET_VARSIZE(v,l) (VARATT_SIZEP(v) = (l))
#endif


PG_FUNCTION_INFO_V1( hello );
Datum
hello(PG_FUNCTION_ARGS)
{
text *arg1 = PG_GETARG_TEXT_PP(0);
text *arg2 = PG_GETARG_TEXT_PP(1);
int32 arg1_size = VARSIZE_ANY_EXHDR(arg1);
int32 arg2_size = VARSIZE_ANY_EXHDR(arg2);
int32 new_text_size = arg1_size + arg2_size + VARHDRSZ;
text *new_text = (text *) palloc(new_text_size);
SET_VARSIZE(new_text, new_text_size);
memcpy(VARDATA(new_text), VARDATA_ANY(arg1), arg1_size);
memcpy(VARDATA(new_text) + arg1_size, VARDATA_ANY(arg2), arg2_size);
PG_RETURN_TEXT_P(new_text);
}

CREATE OR REPLACE FUNCTION
hello( TEXT )
RETURNS
TEXT
AS
'/usr/local/postgresql/share/extension/example.so', 'hello'
LANGUAGE
C
STRICT
IMMUTABLE;


#include "postgres.h"
#include "executor/executor.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PG_FUNCTION_INFO_V1(coverpaid);
Datum
c_overpaid(PG_FUNCTION_ARGS)
{
/* #define PG_GETARG_HEAPTUPLEHEADER(n) DatumGetHeapTupleHeader(PG_GETARG_DATUM(n)) */
HeapTupleHeader t = PG_GETARG_HEAPTUPLEHEADER(0);    /* HeapTupleHeader t_data; -> tuple header and data */
int32 limit = PG_GETARG_INT32(1);
bool isnull;
Datum salary;
salary = GetAttributeByName(t, "salary", &isnull);
if (isnull)
PG_RETURN_BOOL(false);
PG_RETURN_BOOL(DatumGetInt32(salary) < limit);
}

/*

we read our tuple arguments in to rt and lt, using the PG_GETARG_HEAPTUPLEHEADER macro.
使用 PG_GETARG_HEAPTUPLEHEADER 宏将我们的元组参数读入rt和lt中。

GetAttributeByName 是返回指定行的属性的PostgreSQL系统函数。它有三个参数:类型为HeapTupleHeader的传入参数、
想要访问的函数名以及一个说明该属性是否为空的返回参数。
GetAttributeByName返回一个Datum值,可以把它用合适的DatumGetXXX() 宏转换成正确的数据类型。
注意如果空值标志被设置,那么返回值是没有意义的,所以在对结果做任何事情之前应该先检查空值标志。

*/

CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean 
AS '/usr/local/postgresql/share/extension/c_overpaid.so', 'c_overpaid' 
LANGUAGE C STRICT;


--


PostgreSQL › PostgreSQL - interfaces
Login Register
help needed on SPI_modifytuple.
‹ Previous Topic Next Topic ›

classic Classic list List threaded Threaded
3 messages Options Options
fabrizio picca
Reply | Threaded | More
Selected post Sep 12, 2005; 11:17pm
help needed on SPI_modifytuple.
fabrizio picca
4 posts

I'm trying to sipmply modify a tuple before it will be inserted in the db.
The problem is that when it try to insert the modified tuple with
SPI_modifytuple all i get is a SPI_ERROR_ARGUMENT negative (-6) .
Could someone help me? i'm reallygoing crazy.

885

What i did is just a fire-before C trigger that acts like this:

#include <postgres.h>
#include <executor/spi.h> /* this is what you need to work with SPI */
#include <commands/trigger.h> /* ... and triggers */
#include <string.h>
#include <utils/builtins.h>

extern Datum trgupdana(PG_FUNCTION_ARGS);
char *checkFieldData(char *);

PG_FUNCTION_INFO_V1(trgupdana);

Datum
trgupdana(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
TupleDesc tupdesc;
HeapTuple rettuple,
oldtuple,
newtuple;
char *rs1,*rs2,*rs3,*relname;
int ret, i,j;
bool isnull;
Relation rel;

/* make sure it's called as a trigger at all */
if (!CALLED_AS_TRIGGER(fcinfo)) elog(ERROR, "trgchkneg: not called
by trigger manager\n");

if (TRIGGER_FIRED_BEFORE(trigdata->tg_event) &&
TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) //trigger fires when called
for rows and before updating
{
tupdesc = trigdata->tg_relation->rd_att;
oldtuple = trigdata->tg_trigtuple;
newtuple = trigdata->tg_newtuple;
rettuple=NULL;
rel = trigdata->tg_relation;
relname= SPI_getrelname(rel);

if ((ret = SPI_connect()) < 0) // SPI
manager initialization
elog(NOTICE, "trgCheckNeg : SPI_connect returned: %d", ret);

rs1=SPI_getvalue(oldtuple,tupdesc,5);
rs2=SPI_getvalue(oldtuple,tupdesc,6);

elog(NOTICE,"%s,%s",rs1,rs2);

int attnum;
Datum new_value;

rs3=(char*) malloc(80);
sprintf(rs3,"");

if(rs1[strlen(rs1)-1]=='&'){
rs3=strncat(rs3,rs1,strlen(rs1)-1);
rs3=strcat(rs3,"E ");
}
else if(rs1[strlen(rs1)-1]==',')
{
rs3=strncat(rs3,rs1,strlen(rs1)-1);
rs3=strcat(rs3,", ");
} else {
rs3=rs1;
}

elog(NOTICE,"1:%s",rs3);

if(strlen(rs2)!=0){
rs3=strcat(rs3,rs2);
}

elog(NOTICE,"2:%s",rs3);

new_value=DirectFunctionCall1(textin,PointerGetDatum(checkFieldData(rs3)));

attnum=SPI_fnumber(tupdesc,"ARGSL1");

if(rel==NULL) elog(NOTICE,"rel NULL");
if(&attnum==NULL) elog(NOTICE,"attnum NULL");
if(&new_value==NULL) elog(NOTICE,"new_value NULL");
if(&isnull==NULL) elog(NOTICE,"isnull NULL");

rettuple=SPI_modifytuple(rel,newtuple,1,&attnum,&new_value,&isnull);
if(rettuple==NULL){
elog(ERROR,"trgupdana (%s):%d returned by SPI_modifytuple",relname,SPI_result);
}
return PointerGetDatum(rettuple);
}
return PointerGetDatum(oldtuple);
}

char *checkFieldData(char *valueToCheck){
int i=0;
for(i=0;i<strlen(valueToCheck);i++)
{
if(valueToCheck[i]=='&') valueToCheck[i]='E';
else if(valueToCheck[i]=='\'') valueToCheck[i]=96;
}

return(valueToCheck);
}

 


-- 
http://www.fabpicca.net

---------------------------(end of broadcast)---------------------------
TIP 5: don't forget to increase your free space map settings
Michael Fuhr
Reply | Threaded | More
Sep 13, 2005; 12:36am
Re: help needed on SPI_modifytuple.
Michael Fuhr
2113 posts

On Mon, Sep 12, 2005 at 04:17:33PM +0200, fabrizio picca wrote:
> I'm trying to sipmply modify a tuple before it will be inserted in the db.

> The problem is that when it try to insert the modified tuple with
> SPI_modifytuple all i get is a SPI_ERROR_ARGUMENT negative (-6) .
> Could someone help me? i'm reallygoing crazy.
>
> What i did is just a fire-before C trigger that acts like this:

Is the trigger fired on INSERT, UPDATE, or both?

> oldtuple = trigdata->tg_trigtuple;
> newtuple = trigdata->tg_newtuple;
[...]
> rettuple=SPI_modifytuple(rel,newtuple,1,&attnum,&new_value,&isnull);

I didn't look closely at the rest of the code, but if the trigger
is fired on INSERT then you should pass oldtuple; newtuple will be

NULL, causing SPI_modifytuple() to fail with SPI_ERROR_ARGUMENT.
Here's an extract from "Writing Trigger Functions in C" in the
"Triggers" chapter of the documentation:

tg_trigtuple

A pointer to the row for which the trigger was fired. This is
the row being inserted, updated, or deleted. If this trigger was
fired for an INSERT or DELETE then this is what you should return
from the function if you don't want to replace the row with a
different one (in the case of INSERT) or skip the operation.

tg_newtuple

A pointer to the new version of the row, if the trigger was fired
for an UPDATE, and NULL if it is for an INSERT or a DELETE. This

is what you have to return from the function if the event is an
UPDATE and you don't want to replace this row by a different one
or skip the operation.

-- 
Michael Fuhr

---------------------------(end of broadcast)---------------------------
TIP 3: Have you checked our extensive FAQ?

http://www.postgresql.org/docs/faq
fabrizio picca
Reply | Threaded | More
Sep 13, 2005; 1:01am
Re: help needed on SPI_modifytuple.
fabrizio picca
4 posts

thanks a lot Michael, you've just hit the problem!
Now everything works fine!

Thanks again
Fabrizio

On 9/12/05, Michael Fuhr <[hidden email]> wrote:

> On Mon, Sep 12, 2005 at 04:17:33PM +0200, fabrizio picca wrote:
> > I'm trying to sipmply modify a tuple before it will be inserted in the db.
> > The problem is that when it try to insert the modified tuple with
> > SPI_modifytuple all i get is a SPI_ERROR_ARGUMENT negative (-6) .
> > Could someone help me? i'm reallygoing crazy.
> >
> > What i did is just a fire-before C trigger that acts like this:
>
> Is the trigger fired on INSERT, UPDATE, or both?
>
> > oldtuple = trigdata->tg_trigtuple;
> > newtuple = trigdata->tg_newtuple;
> [...]
> > rettuple=SPI_modifytuple(rel,newtuple,1,&attnum,&new_value,&isnull);
>
> I didn't look closely at the rest of the code, but if the trigger
> is fired on INSERT then you should pass oldtuple; newtuple will be
> NULL, causing SPI_modifytuple() to fail with SPI_ERROR_ARGUMENT.
> Here's an extract from "Writing Trigger Functions in C" in the
> "Triggers" chapter of the documentation:
>
> tg_trigtuple
>
> A pointer to the row for which the trigger was fired. This is
> the row being inserted, updated, or deleted. If this trigger was
> fired for an INSERT or DELETE then this is what you should return
> from the function if you don't want to replace the row with a
> different one (in the case of INSERT) or skip the operation.
>
> tg_newtuple
>
> A pointer to the new version of the row, if the trigger was fired
> for an UPDATE, and NULL if it is for an INSERT or a DELETE. This
> is what you have to return from the function if the event is an
> UPDATE and you don't want to replace this row by a different one
> or skip the operation.
>
> --
> Michael Fuhr
>
... [show rest of quote]


-- 
L'Estetica del lavoro è lo spettacolo della merce umana (Area)
-- 
http://www.fabpicca.net

---------------------------(end of broadcast)---------------------------
TIP 9: In versions below 8.0, the planner will ignore your desire to
choose an index scan if your joining column's datatypes do not
match
« Return to PostgreSQL - interfaces | 218 views
Free forum by Nabble Disable Ads | Edit this page


---

SPI_getrelname

SPI_modifytuple
SPI_modifytuple — 通过替换一个给定行的选定域来创建一行
大纲
HeapTuple SPI_modifytuple(Relation rel, HeapTuple row, int ncols,
int * colnum, Datum * values, const char * nulls)
描述
SPI_modifytuple创建一个新行,其中选定的列 用新值替代,其他列则从输入行中拷贝。输入
行本身不被修改。 新行在上层执行器上下文中返回。
该函数只能在连接到SPI时使用。否则,它会返回NULL并将SPI_result 设置
为SPI_ERROR_UNCONNECTED。
参数
Relation rel
只被用作该行的行描述符的来源(传递一个关系而不是 一个行描述符是一种令人头痛的
设计)。
HeapTuple row
要被修改的行
int ncols
要被修改的列数
int * colnum
一个长度为ncols的数组,包含了要被修改的列号 (列号从 1 开始)
Datum * values
一个长度为ncols的数组,包含了指定列的新值
const char * nulls
一个长度为ncols的数组,描述哪些新值为空值
如果nulls为NULL,那么 SPI_modifytuple假定没有新值为空值。否则, 如果对应的新值
为非空,nulls数组的每一项都应 该是' ',而如果对应的新值为空值则为'n'(在 后一种情
况中,对应的values项中的新值无关紧 要)。注意nulls不是一个文本字符串,只是一个
数组:它不需要一个'\0'终止符。
返回值
应用了修改的新行,在上层执行器上下文中分配,或者错误时为 NULL(参阅SPI_result获取
错误指示)
出错时,SPI_result被设置如下:
SPI_ERROR_ARGUMENT
如果rel为NULL,或者 row为NULL,或者ncols 小于等于 0,或者colnum为NULL, 或 者values为NULL。

SPI_ERROR_NOATTRIBUTE
如果colnum包含一个无效的列号(小于等于 0 或者大于 row中的列数)。
SPI_ERROR_UNCONNECTED
如果SPI未激活


--

Before we begin, let's look at what we want to accomplish. Let's say we'd like to create a set of PostgreSQL functions that implement the features of Mark Galassi's excellent GNU Scientific Library. Let's pick one of the library's functions, gsl_complex_add, and see what we need to do to create a corresponding PostgreSQL function. When we're finished, we'll be able to write SQL statements like this:

> select gsl_complex_add( ROW( 3.2e4, -3.2 ), ROW( 4.1, 4.245e-3 ) );

gsl_complex_add 
---------------------
(32004.1,-3.195755)

I think it's appropriate to represent complex numbers in PostgreSQL as tuples, where the real and imaginary components get passed around together as a pair. Think of a tuple as a structure in C. The tuple concept jibes with the way we're taught to think about these things in other domains. We'll be using PostgreSQL's CREATE TYPE statement to define the composite type we use as follows:

DROP FUNCTION gsl_complex_add ( __complex, __complex );
DROP TYPE __complex;

CREATE TYPE __complex AS ( r float, i float );

CREATE OR REPLACE FUNCTION
gsl_complex_add( __complex, __complex )
RETURNS
__complex
AS
'example.so', 'c_complex_add'
LANGUAGE
C
STRICT

// PostgreSQL includes
#include "postgres.h"
#include "fmgr.h"
// Tuple building functions and macros
#include "access/heapam.h"
#include "funcapi.h"

#include <string.h>

// GNU Scientific Library headers
#include <gsl/gsl_complex.h>
#include <gsl/gsl_complex_math.h>

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

// forward declaration to keep compiler happy
Datum c_complex_add( PG_FUNCTION_ARGS );

PG_FUNCTION_INFO_V1( c_complex_add );
Datum
c_complex_add( PG_FUNCTION_ARGS )
{
// input variables
HeapTupleHeader lt, rt;

bool isNull;
int tuplen;
bool *nulls;

// things we need to deal with constructing our composite type
TupleDesc tupdesc;    /* 行描述符 */
Datum values[2];
HeapTuple tuple;

// See PostgreSQL Manual section 33.9.2 for base types in C language
// functions, which tells us that our sql 'float' (aka 'double
// precision') is a 'float8 *' in PostgreSQL C code.
float8 *tmp;

// defined by GSL library
gsl_complex l, r, ret;

// Get arguments. If we declare our function as STRICT, then
// this check is superfluous.
if( PG_ARGISNULL(0) ||
PG_ARGISNULL(1) )
{
PG_RETURN_NULL();
}

/*
首先,我们使用 PG_GETARG_HEAPTUPLEHEADER 宏读取rt和lt中的元组参数。
然后,使用 GetAttributeByNum 函数从元组中选择组件值。
*/
// Get components of first complex number
//// get the tuple
lt = PG_GETARG_HEAPTUPLEHEADER(0);
////// get the first element of the tuple
tmp = (float8*)GetAttributeByNum( lt, 1, &isNull );
if( isNull ) { PG_RETURN_NULL(); }
GSL_SET_REAL( &l, *tmp );
////// get the second element of the tuple
tmp = (float8*)GetAttributeByNum( lt, 2, &isNull );
if( isNull ) { PG_RETURN_NULL(); }
GSL_SET_IMAG( &l, *tmp );

// Get components of second complex number
rt = PG_GETARG_HEAPTUPLEHEADER(1);
tmp = (float8*)GetAttributeByNum( rt, 1, &isNull );
if( isNull ) { PG_RETURN_NULL(); }
GSL_SET_REAL( &r, *tmp );
tmp = (float8*)GetAttributeByNum( rt, 2, &isNull );
if( isNull ) { PG_RETURN_NULL(); }
GSL_SET_IMAG( &r, *tmp );

// Example of how to print informational debugging statements from
// your PostgreSQL module. Remember to set minimum log error
// levels appropriately in postgresql.conf, or you might not
// see any output.
ereport( INFO,
( errcode( ERRCODE_SUCCESSFUL_COMPLETION ),
errmsg( "tmp: %e\n", *tmp )));

// call our GSL library function
ret = gsl_complex_add( l, r );

// Now we need to convert this value into a PostgreSQL composite type.

/* 为结果类型构造一个元组描述符 */
if( get_call_result_type( fcinfo, NULL, &tupdesc ) != TYPEFUNC_COMPOSITE )    // fcinfo: "fmgr.h" Postgres函数管理器和函数调用接口的定义。
//该文件必须包含在所有定义或调用fmgr-callable函数的Postgres模块中。
// get_call_result_type: funcapi.c fmgr函数的实用程序和便捷函数,这些函数返回集和/或复合类型,或处理VARIADIC输入。
ereport( ERROR,
( errcode( ERRCODE_FEATURE_NOT_SUPPORTED ),
errmsg( "function returning record called in context "
"that cannot accept type record" )));

// Use BlessTupleDesc if working with Datums. Use
// TupleDescGetAttInMetadata if working with C strings (official
// 8.2 docs section 33.9.9 shows usage)
/*
在处 理 Datum 时,需要把该TupleDesc传递给 BlessTupleDesc,
接着为每一行调用 heap_form_tuple 
*/
BlessTupleDesc( tupdesc );

// WARNING: Architecture specific code!
// GSL uses double representation of complex numbers, which
// on x86 is 8 bytes. 
// Float8GetDatum defined in postgres.h.
values[0] = Float8GetDatum( GSL_REAL( ret ) );    // 通过使用Float8GetDatum函数,我们会将GSL可以理解的数据转换回PostgreSQL可以理解的格式。
values[1] = Float8GetDatum( GSL_IMAG( ret ) );

tuplen = tupdesc->natts;    /* number of attributes in the tuple */
nulls = palloc( tuplen * sizeof( bool ) );

// build tuple from datum array
tuple = heap_form_tuple( tupdesc, values, nulls );

pfree( nulls );

// A float8 datum palloc's space, so if we free them too soon,
// their values will be corrupted (so don't pfree here, let
// PostgreSQL take care of it.)
// pfree(values);

PG_RETURN_DATUM( HeapTupleGetDatum( tuple ) );
}


在我看到 HeapTupleHeader 变量lt和rt的声明(对于“ left tuple”和“ right tuple”)之前,
相对于我的上一篇文章,这里没有太多新的事情。
在这里,我们不将简单的数据类型作为参数, 
正在使用我们在CREATE TYPE语句中定义的元组参数。 
我们的每个元组都有两个双精度分量,
分别表示复数的实数和虚数分量。

这些函数从给定的元组Datum中返回请求的属性的值。
以元组为参数的C函数应该使用它们。 例如:overpaid(EMP)可能会调用 GetAttributeByNum ()。
注意:这些实际上很慢,因为它们在每次调用时都会进行 typcache 查找。

GetAttributeByName 是返回指定行的属性的 PostgreSQL系统函数。它有三个参数: 类型
为HeapTupleHeader的传入参数、想要访问的函数名 以及一个说明该属性是否为空的返回参
数。 GetAttributeByName 返回一个Datum 值,可以把它用合适的DatumGetXXX() 宏转换成正
确的数据类型。注意如果空值标志被设置,那么返回值是没有 意义的,所以在对结果做任何
事情之前应该先检查空值标志。

碰巧我们的GSL库的复数函数期望输入“双”值,在我正在运行的x86 Linux平台上,
该值方便地为8个字节,并直接映射到PostgreSQL使用的float8值。 请在这里密切注意,因为如果您的数据类型映射不正确,您会头疼。

get_call_result_type:
get_call_result_type 可以解析多态函数结果的实际类型; 
因此,它在返回标量多态结果的函数中很有用,
不仅是返回合成的函数。 
resultTypeId输出主要用于返回多态标量的函数。
给定一个函数的调用信息记录,确定应返回的数据类型。 
如果resultTypeId不为NULL,
则* resultTypeId会接收实际的数据类型OID(这主要用于标量结果类型)。 
如果resultTupleDesc不为NULL,
则* resultTupleDesc会在结果为复合类型时接收到指向TupleDesc的指针,
而在其为标量结果时会接收到NULL。
检查结果是否为 TYPEFUNC_COMPOSITE ; 
如果是这样,则resultTupleDesc已填充所需的TupleDesc。

 

 

 

 

 

 

 

 

 

 

posted on 2020-02-12 15:37  黑洞中的奇点  阅读(747)  评论(0编辑  收藏  举报