postgresql spi开发笔记
黑洞中的奇点 人气:8
#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 "utilshttps://img.qb5200.com/download-x/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/extensionhttps://img.qb5200.com/download-x/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.orghttps://img.qb5200.com/download-x/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。
加载全部内容