pqt-handlers - A manual for implementing libpqtypes type handlers.
Contents
Composites
To get and put composites, they must be registered. During registration, information about the composite
type, likes its OID and attributes, are looked up in the backend. The composite must exist or the
registration fails. Do a `man pqt-composites(3)´ for a more information about composites.
Registering a composite type:
CREATETYPEsimpleAS(aint4,ttext);PGregisterTypetype={"simple",NULL,NULL};PQregisterTypes(conn,PQT_COMPOSITE,&type,1,0);
*) The put and get routines must be NULL, composites cannot be sub-classed
*) The provided name cannot resolve to the backend´s RECORDOID
*) The composite must exist at "conn"
*) If no schema name is provided, the composite must be within the backend´s search path.
During registration of a composite, the below information is retreived from the backend:
*) Oid of the composite type
*) Array Oid of the composite type
*) Type len of the compsoite type, PQfsize
For each composite attribute:
*) Oid of the attribute
*) Name of the attribute
*) Type len of the attribute, PQfsize
*) The typmod of the attribute, PQfmod
Copyright
Copyright (c) 2011 eSilo, LLC. All rights reserved.
This is free software; see the source for copying conditions. There is NO warranty; not even for
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Description
Type handlers are I/O routines used by libpqtypes for sending and receiving data for specific types.
Internally, libpqtypes uses type handlers to support PostgreSQL's builtin base types: such as point,
int4, timestamp, etc...
NOTE: Builtin types always serialize parameters being sent "put" to the backend in binary format; user-
defined types may choose to use text format. C data types are translated into the backend's external
binary format. Even if text results are used, C data types are still exposed when getting result data.
Typehandlershavethebelowthreeproperties:TypeSpecifierName
A [schema].type name that will be used to reference your handler. `man pqt-specs´ for complete
documentation on syntax.
PutRoutine
A PGtypeProc put routine takes a C data type and converts it into a valid backend external format.
The converted format is used with libpq's parameterized API. For instance: a C int data type is
used to put a postgresql int4. To convert this to a valid external format, libpqtypes swaps the
bytes (when needed) so they are in network order. A put routine returns the number of bytes being
put. On error, a put routine must return -1.
GetRoutine
A PGtypeProc get routine does the opposite of a put routine. It converts a type's text or binary
external format to its native C type. For instance: a postgresql int4 is converted to a C int.
For binary results, the 4 bytes are converted to host order and stored as a C int. A get routine
returns zero to indicate success. On error, a get routine must return -1.
PGregisterType
The PGregisterType structure is used by all PQregisterXXX functions. It contains a typname, put and get
routine. The typname can optionally contain the type's schema, like pg_catalog.int4. When registering a
sub class via PQregisterTypes, an inheritence operator must be used within typname to indicate what type
is being extended: myint4=int4. If the typput and typget routines are NULL during a sub class
registration, the result is a direct sub class or alias of the base type: like "s=text" allowing one to
use "%s" instead of "%text". When registering a composite, typput and typget are ignored.
typedefstruct{constchar*typname;PGtypeProctypput;PGtypeProctypget;}PGregisterType;
To implement a type handler, you need to be aware of 4 structures: PGtypeFormatInfo, PGrecordAttDesc,
PGtypeHandler and PGtypeArgs. All exist for use with type handlers.
PGtypeFormatInfo
The PGtypeFormatInfo structure provides useful connection-based information for type handlers. For
instance, your handler may have different implementations depending on the server version .. sversion.
typedefstruct{intsversion;/*serverversion,e.g.70401for7.4.1*/intpversion;/*FE/BEprotocolversioninuse*/chardatestyle[32];/*server´sdatestyle:like"SQL,MDY"*//*Whennon-zero,serverusesint64timestamps*/intinteger_datetimes;}PGtypeFormatInfo;PGrecordAttDesc
The PGrecordAttDesc structure defines the attributes of a composite. Internally, libpqtypes keeps track
of composite attributes using this structure.
typedefstruct{Oidattoid;/*Oidoftheattribute*/intattlen;/*storagesizeofattribute.-1ifnotknown*/intatttypmod;/*Thetypmodofattribute.*/char*attname;/*Thenameoftheattribute.*/}PGrecordAttDesc;PGtypeHandler
The PGtypeHandler structure represents all the properties of a type handler. When a type is registered,
this structure is used to catalog the type´s information.
typedefstructpg_typhandler{/*Aninternallibpqtypesassignedidforthistypehandler.*/intid;/*Theschemanameofthistype,whichmaybeemptyifnot*providedduringregistration.*/chartypschema[65];/*Thenameofthistype:likeint2orbytea,cannotbeempty*/chartypname[65];/*Thestoragesizeofthistype.-1ifnotknown.*/inttyplen;/*ThebackendOIDofthetype.*/Oidtypoid;/*ThebackendarrayOIDofthetype.*/Oidtypoid_array;/*Theputhandlerforthistype.*/PGtypeProctypput;/*Thegethandlerforthistype.*/PGtypeProctypget;/*Ifthishandlerisasub-class,thiswillbethe´id´of*thesuperclasstypehandler.Itissetto-1ifnot*asub-class.*/intbase_id;/*Indicatesthenumberofcompositeattributeswithinthe*´attDescs´array.Thisissetto0fornon-composites.*/intnattrs;/*Ifnon-zero,the'attDescs'pointermustbefreed.*/intfreeAttDescs;/*Thememorybehindthe'attDescs'pointerwhenthenumberof*attrsislessthan16.Whengreaterthan16,heapmemory*isusedand'freeAttDescs'issettoanon-zerovalue.*/PGrecordAttDescattDescsBuf[16];/*AnarrayofPGrecordAttDesc,oneelementperrecord*attribute.Mustbefreedif'freeAttDescs'isnon-zero.*/PGrecordAttDesc*attDescs;}PGtypeHandler;PGtypeArgs
The PGtypeArgs structure is passed to all put and get handlers. It contains all values needed by type
handlers.
structpg_typeargs{/*Indicatesifthisisaputorgetoperation.*/intis_put;/*Formattinginformation.*/constPGtypeFormatInfo*fmtinfo;/*Indicatesifarequestforadirectpointerwas*made,%text*.*/intis_ptr;/**Whenis_putisnon-zero,setthisto1forbinaryand0for*textformat.Itdefaultstobinary.Whenis_putis0,this*indicatesthefieldtypePQftypeofget.field_num.*/intformat;/*Anargumentlist.Argumentsshouldberetrievedwithva_arg.*/va_listap;/*Thepositionofthistypnamewithinaspecifier*string,1-based.*/inttyppos;/*Typehandlerforthespecifierattyppos.*/PGtypeHandler*typhandler;/**Reportanerrorfromwithinahandler.Thiserrormessage*willshowupinPQgeterror.**Thisalwaysreturns-1soonecanreportanerrorandreturn*-1fromahandlerinasinglestatement:**returnargs->errorf(args,"ERROR:%s",strerror(errno));**errorfalwaysprependsasmallheader*"schema.typname[pos:num]-msg".Forexample,iftheabove*failedwithintheint4handlerandtypposwas5,the*resultingerrormessagewouldbe:**pg_catalog.int4[pos:5]-ERROR:Invalidargument**errorfdoesnotputanynewlinesinerrormessage.*/int(*errorf)(PGtypeArgs*args,constchar*format,...);/*Usedbytypesub-classhandlers.Whenis_putis*non-zero,asub-classpreparestypedataandthencalls*super.Whenis_putiszero,asub-classfirst*callssupertogetthebaseclass'sdeserializedvalue*andcanthenconvertit.*/int(*super)(PGtypeArgs*args,...);/*Thisstructureisusedwhenis_putisnon-zero.*/struct{/*ThePGparamstructurepassedtoPQputf().*/PGparam*param;/*Abufferusedtostorethetype'soutputformat.If*morethan'outl'bytesareneeded,see'expandBuffer'.*Normallydataiscopiedtotheoutbuffer,butitcan*alsobepointedelsewhere:likeaconststringorstatic*memory.Whenrepointingtheoutbuffer,DONOTuse*'expandBuffer'.Neveruserealloconthisbuffer.*/char*out;/*Thesizeinbytesofthe'out'buffer.IfexpandBuffer*isused,thiswillreflectthenewbufferlength.*/intoutl;/*Expandsthe'out'bufferto'new_len'.Ifnew_lenis*lessthanorequaltothecurrentlength'outl',the*expandrequestisignored.Thisbehavesjustlikea*realloc,existingdataiscopiedtothenewmemory.*Youshouldneverusereallocontheoutbuffer.*Returns-1onerrorand0forsuccess.*/int(*expandBuffer)(PGtypeArgs*args,intnew_len);/*internaluseonly.*/char*__allocated_out;}put;/*Thisstructureisusedwhenis_putiszero.*/struct{/*ThePGresultpassedtoPQgetf().PGresult*result;/*Thetuplenumber*/inttup_num;/*thetuplefieldnumber.*/intfield_num;}get;};Examples
None.
Name
pqt-handlers - A manual for implementing libpqtypes type handlers.
Reporting Bugs
Report bugs to <libpqtypes@esilo.com>.
See Also
PQregisterTypes(), PQregisterResult()
libpqtypes 2011 pqt-handlers(3)
Type Sub-Classing
Sub-classing a type means extending the put or get routines of a registered type handler. The idea came
about from trying to provide a convention for registering domains; which amounts to simple aliases to
libpqtypes. Domain/alias registration would look like this:
PGregisterTypetype={"myint4=pg_catalog.int4",NULL,NULL};PQregisterTypes(conn,PQT_SUBCLASS,&type,1,0);
The 'typname' member syntax is: [schema].type=[base_schema].base_type (schema is optional). No spaces
are allowed unless contained within the schema or type name, which would require double quoting the
identifer. By passing NULL for both the put and get handlers, the base type's handlers are used. Thus,
the result of the above is that "%myint4" and "%int4" behave identically. But what happens if a put or
get handler is provided during an alias registration? Is this useful functionality to applications? The
answer is sub-classing and yes its useful.
By providing a put and get handler during alias registration, one has effectively sub-classed the base
type. This is called sub-class registration.
By sub-classing a registered type, applications can now put and get data using their own data structures.
The sub-class put and get routines handle the dirty work of converting application structures to the base
type's structure. When sub-classing, no oid lookup occurs with the server. The sub-class type is
assumed to be application specific. Sub-classes are registered on a per connection basis, just like
user-defined types. The reason for this is because the base type can be server-specific.
BENEFITS
1. Centralizes conversions from application data types to libpq data types
2. Provides an easy all-inclusive interface for putting and getting values
3. Allows applications to piggy-back off libpqtypes internal binary and text convertors
4. Adds enormous flexiblity: (a few interesting ideas)
-- %socket: sub-class the inet get routine and return a connected sockfd.
-- %file: sub-class the text get routine and return a FILE* (text being a pathname)
-- %filemd5: sub-class the bytea put routine and supply a pathname that is used to
md5 a file's contents, utlimately putting a 16 byte bytea.
It is impossible to consider all of the uses for type sub-classing. The above ideas are probably more
extreme than common cases, such as taking an application struct and converting it to what the base type
expects. But, the extreme cases are possible when desired.
Sub-classexample
Assume you have an application that works with time_t epoch values a lot. It would be useful if you
could define a %epoch type handler. This avoids having to convert a time_t to either a string or to a
PGtimestamp (used by the timestamp & timestamptz type handlers). The problem is, to use the binary
interface you would have to know how to serialize a timestamp to send/recv it from the server. If you
sub-class timestamptz, you can use PGtypeArgs.super to handle the dirty work.
**NOTE: %epoch is only an example, it is not part of libpqtypes nor being proposed. The goal here is to
demonstrate how to implement a type sub-class handler. It is important to note that %epoch will announce
itself as a timestamptz to the backend. So when using %epoch, make sure the context allows a
timestamptz.
/*wearegoingtoregisterthisunderthe´pqt´schema*/PGregisterTypetype={"pqt.epoch=pg_catalog.timestamptz",epoch_put,epoch_get};PQregisterTypes(conn,PQT_SUBCLASS,&type,1,0))/*puttinganepoch*/structstatst;if(stat("/home/foobar/archive.tgz",&st)==0){PGparam*param=PQparamCreate(conn);PQputf(param,"%epoch",st.st_mtime);//....}/*gettinganepochvalue,usingfullyqualifiedtypename*/structutimbufut={0,0};PQgetf(result,tup_num,"%pqt.epoch",field_num,&ut.modtime);/*-------------------------------*EXAMPLEEPOCHSUB-CLASSIMPLEMENTATION*//*convertatime_ttoaPGtimestampandcallargs->super()*/intepoch_put(PGtypeArgs*args){structtm*tm;PGtimestampts;time_tt=va_arg(args->ap,time_t);tm=localtime(&t);ts.date.isbc=0;ts.date.year=tm->tm_year+1900;/*always4-digityear*/ts.date.mon=tm->tm_mon;ts.date.mday=tm->tm_mday;ts.time.hour=tm->tm_hour;ts.time.min=tm->tm_min;ts.time.sec=tm->tm_sec;ts.time.usec=0;ts.time.gmtoff=tm->tm_gmtoff;/*Internally,thiscallsthebasetype´sputroutine*(thesuperclass).Inthiscase,thesuperclass*expectsaPGtimestampasinput.Thesuperfunction*returnswhateverthebasetype´sputroutinereturns*(whichforallputsisthebytecountor-1onerror).*/returnargs->super(args,&ts);}/*Callsargs->super()togetaPGtimestampandthenconverts*ittoatime_tvalue.*/intepoch_get(PGtypeArgs*args){PGtimestampts;time_t*t=va_arg(args->ap,time_t*);if(!t)returnargs->errorf(args,"time_t*cannotbeNULL");/*zerouserbits*/*t=0;/*Internally,thiscallsthebasetype´sgetroutine,*whichreturns0or-1onerror.*/if(args->super(args,&ts)==-1)return-1;/*args->errorfcalledbysuperalready*//*SincePGtimestampcontainsanepochmember,wecan*justcopythatvalueratherthancallingmktime().*/*t=(time_t)ts.epoch;return0;}User-Defined Types
User-defined types are extended base types in the backend. They are not domains or composites. These
types have their own input/output and send/recv functions (normally written in C). They normally include
their own operator functions and have an array oid. For libpqtypes to make use of these types,
especially for binary puts and gets, a type handler must be registered. This provides libpqtypes with a
type specifer, put and get routines for handling this type.
User-defined types are registered on a per connection basis and must exist on the server. If the type
does not exist, the registration fails. If no schema name is provided during registration, the server's
search path is used to resolve the type's existence and fetch its oid. If a schema name is provided
during registration, the search path is not used.
User-definedtypeexample
Assume there is a user-defined type named ´rgb´ in the ´graphics´ schema. The text output format is
always in hex: ´#ff0000´ with a leading pound sign and lowercase hex digits. The external binary format
is a sequence of three unsigned bytes: r, g and b. To use this type with libpqtypes, it must be
registered.
/*registerthergbtype*/PGregisterTypetype={"graphics.rgb",rgb_put,rgb_get};PQregisterTypes(conn,PQT_USERDEFINED,&type,1,0);/*putanrgb*/rgb_trgb={218,218,218};PGparam*param=PQparamCreate(conn);PQputf(param,"%rgb",&rgb);/*getanrgbfromtuple0field4*/rgb_trgb;PQgetf(result,0,"%graphics.rgb",4,&rgb);/*-------------------------------*EXAMPLERGBIMPLEMENTATION*/#definehex2dec(v)(unsignedchar)(((v)>'9')?((v)-'a')+10:(v)-'0')/*examplergbstruct*/typedefstruct{unsignedcharr;unsignedcharb;unsignedcharg;}rgb_t;/*RGBPGtypeProchandler-alwaysputsinbinaryformat*/intrgb_put(PGtypeArgs*args){unsignedchar*out;rgb_t*rgb=va_arg(args->ap,rgb_t*);/*IfrgbisNULL,putanSQLNULLvalue*/if(!rgb){args->put.out=NULL;return0;}/*writethe3bytestotheargsoutbuffer*/out=(unsignedchar*)args->put.out;*out++=rgb->r;*out++=rgb->g;*out=rgb->b;return3;/*numberofbytestheservershouldexpect*/}/*RGBPGtypeProchandler*/intrgb_get(PGtypeArgs*args){rgb_t*rgb=va_arg(args->ap,rgb_t*);char*value=PQgetvalue(args->get.result,args->get.tup_num,args->get.field_num);if(!rgb)returnargs->errorf(args,"rgb*cannotbeNULL");/*textformat:ex.´#ff9966´*/if(PQfformat(args->format)==0){value++;/*skipthe´#´sign*/rgb->r=(hex2dec(value[0])<<4)|hex2dec(value[1]);rgb->g=(hex2dec(value[2])<<4)|hex2dec(value[3]);rgb->b=(hex2dec(value[4])<<4)|hex2dec(value[5]);return0;}/*binaryformat*/rgb->r=(unsignedchar)value[0];rgb->g=(unsignedchar)value[1];rgb->b=(unsignedchar)value[2];return0;}