postgresql spi開發筆記

 

#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。

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章