logo
Free, unlimited AI code reviews that run on commit
git-lrc git-lrc GitHub Install Now We'd appreciate a star git-lrc - Free, unlimited AI code reviews that run on commit | Product Hunt git-lrc - Free, unlimited AI code reviews that run on commit | Product Hunt

pqt-handlers - A manual for implementing libpqtypes type handlers.

Author

       A  contribution  of eSilo, LLC. for the PostgreSQL Database Management System.  Written by Andrew Chernow
       and Merlin Moncure.

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

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;}

See Also