123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992(* PG'OCaml is a set of OCaml bindings for the PostgreSQL database.
*
* PG'OCaml - type safe interface to PostgreSQL.
* Copyright (C) 2005-2009 Richard Jones and other authors.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*)openPGOCaml_auxopenCalendarLibopenPrintfmoduletypeTHREAD=sigtype'atvalreturn:'a->'atval(>>=):'at->('a->'bt)->'btvalfail:exn->'atvalcatch:(unit->'at)->(exn->'at)->'attypein_channeltypeout_channelvalopen_connection:Unix.sockaddr->(in_channel*out_channel)tvaloutput_char:out_channel->char->unittvaloutput_binary_int:out_channel->int->unittvaloutput_string:out_channel->string->unittvalflush:out_channel->unittvalinput_char:in_channel->chartvalinput_binary_int:in_channel->inttvalreally_input:in_channel->Bytes.t->int->int->unittvalclose_in:in_channel->unittendmoduletypePGOCAML_GENERIC=sigtype'at(** Database handle. *)type'amonadtypeisolation=[`Serializable|`Repeatable_read|`Read_committed|`Read_uncommitted]typeaccess=[`Read_write|`Read_only]exceptionErrorofstring(** For library errors. *)exceptionPostgreSQL_Errorofstring*(char*string)list(** For errors generated by the PostgreSQL database back-end. The
* first argument is a printable error message. The second argument
* is the complete set of error fields returned from the back-end.
* See [http://www.postgresql.org/docs/8.1/static/protocol-error-fields.html]
*)(** {6 Connection management} *)typeconnection_desc={user:string;port:int;password:string;host:[`Hostnameofstring|`Unix_domain_socket_dirofstring];database:string}valdescribe_connection:?host:string->?port:int->?user:string->?password:string->?database:string->?unix_domain_socket_dir:string->unit->connection_desc(** Produce the actual, concrete connection parameters based on the values and
* availability of the various configuration variables.
*)valconnection_desc_to_string:connection_desc->string(** Produce a human-readable textual representation of a concrete connection
* descriptor (the password is NOT included in the output of this function)
* for logging and error reporting purposes.
*)valconnect:?host:string->?port:int->?user:string->?password:string->?database:string->?unix_domain_socket_dir:string->?desc:connection_desc->unit->'atmonad(** Connect to the database. The normal [$PGDATABASE], etc. environment
* variables are available.
*)valclose:'at->unitmonad(** Close the database handle. You must call this after you have
* finished with the handle, or else you will get leaked file
* descriptors.
*)valping:'at->unitmonad(** Ping the database. If the database is not available, some sort of
* exception will be thrown.
*)valalive:'at->boolmonad(** This function is a wrapper of [ping] that returns a boolean instead of
* raising an exception.
*)(** {6 Transactions} *)valbegin_work:?isolation:isolation->?access:access->?deferrable:bool->'at->unitmonad(** Start a transaction. *)valcommit:'at->unitmonad(** Perform a COMMIT operation on the database. *)valrollback:'at->unitmonad(** Perform a ROLLBACK operation on the database. *)valtransact:'at->?isolation:isolation->?access:access->?deferrable:bool->('at->'bmonad)->'bmonad(** [transact db ?isolation ?access ?deferrable f] wraps your
* function [f] inside a transactional block.
* First it calls [begin_work] with [isolation], [access] and [deferrable],
* then calls [f] and do [rollback] if [f] raises
* an exception, [commit] otherwise.
*)(** {6 Serial column} *)valserial:'at->string->int64monad(** This is a shorthand for [SELECT CURRVAL(serial)]. For a table
* called [table] with serial column [id] you would typically
* call this as [serial dbh "table_id_seq"] after the previous INSERT
* operation to get the serial number of the inserted row.
*)valserial4:'at->string->int32monad(** As {!serial} but assumes that the column is a SERIAL or
* SERIAL4 type.
*)valserial8:'at->string->int64monad(** Same as {!serial}.
*)(** {6 Miscellaneous} *)valmax_message_length:intref(** Maximum message length accepted from the back-end. The default
* is [Sys.max_string_length], which means that we will try to read as
* much data from the back-end as we can, and this may cause us to
* run out of memory (particularly on 64 bit machines), causing a
* possible denial of service. You may want to set this to a smaller
* size to avoid this happening.
*)valverbose:intref(** Verbosity. 0 means don't print anything. 1 means print short
* error messages as returned from the back-end. 2 means print all
* messages as returned from the back-end. Messages are printed on [stderr].
* Default verbosity level is 1.
*)valset_private_data:'at->'a->unit(** Attach some private data to the database handle.
*
* NB. The pa_pgsql camlp4 extension uses this for its own purposes, which
* means that in most programs you will not be able to attach private data
* to the database handle.
*)valprivate_data:'at->'a(** Retrieve some private data previously attached to the database handle.
* If no data has been attached, raises [Not_found].
*
* NB. The pa_pgsql camlp4 extension uses this for its own purposes, which
* means that in most programs you will not be able to attach private data
* to the database handle.
*)valuuid:'at->stringtypepa_pg_data=(string,bool)Hashtbl.t(** When using pa_pgsql, database handles have type
* [PGOCaml.pa_pg_data PGOCaml.t]
*)(** {6 Low level query interface - DO NOT USE DIRECTLY} *)typeoid=int32typeparam=stringoption(* None is NULL. *)typeresult=stringoption(* None is NULL. *)typerow=resultlist(* One row is a list of fields. *)valprepare:'at->query:string->?name:string->?types:oidlist->unit->unitmonad(** [prepare conn ~query ?name ?types ()] prepares the statement [query]
* and optionally names it [name] and sets the parameter types to [types].
* If no name is given, then the "unnamed" statement is overwritten. If
* no types are given, then the PostgreSQL engine infers types.
* Synchronously checks for errors.
*)valexecute_rev:'at->?name:string->?portal:string->params:paramlist->unit->rowlistmonadvalexecute:'at->?name:string->?portal:string->params:paramlist->unit->rowlistmonad(** [execute conn ?name ~params ()] executes the named or unnamed
* statement [name], with the given parameters [params],
* returning the result rows (if any).
*
* There are several steps involved at the protocol layer:
* (1) a "portal" is created from the statement, binding the
* parameters in the statement (Bind).
* (2) the portal is executed (Execute).
* (3) we synchronise the connection (Sync).
*
* The optional [?portal] parameter may be used to name the portal
* created in step (1) above (otherwise the unnamed portal is used).
* This is only important if you want to call {!PGOCaml.describe_portal}
* to find out the result types.
*)valcursor:'at->?name:string->?portal:string->params:paramlist->(row->unitmonad)->unitmonadvalclose_statement:'at->?name:string->unit->unitmonad(** [close_statement conn ?name ()] closes a prepared statement and frees
* up any resources.
*)valclose_portal:'at->?portal:string->unit->unitmonad(** [close_portal conn ?portal ()] closes a portal and frees up any resources.
*)valinject:'at->?name:string->string->rowlistmonad(** [inject conn ?name query] executes the statement [query]
* and optionally names it [name] and gives the result.
*)valalter:'at->?name:string->string->unitmonad(** [alter conn ?name query] executes the statement [query]
* and optionally names it [name]. Same as inject but ignoring the result.
*)typeresult_description={name:string;(** Field name. *)table:oidoption;(** OID of table. *)column:intoption;(** Column number of field in table. *)field_type:oid;(** The type of the field. *)length:int;(** Length of the field. *)modifier:int32;(** Type modifier. *)}typerow_description=result_descriptionlisttypeparam_description={param_type:oid;(** The type of the parameter. *)}typeparams_description=param_descriptionlistvaldescribe_statement:'at->?name:string->unit->(params_description*row_descriptionoption)monad(** [describe_statement conn ?name ()] describes the named or unnamed
* statement's parameter types and result types.
*)valdescribe_portal:'at->?portal:string->unit->row_descriptionoptionmonad(** [describe_portal conn ?portal ()] describes the named or unnamed
* portal's result types.
*)(** {6 Low level type conversion functions - DO NOT USE DIRECTLY} *)valname_of_type:oid->string(** Returns the OCaml equivalent type name to the PostgreSQL type [oid].
* For instance, [name_of_type (Int32.of_int 23)] returns ["int32"] because
* the OID for PostgreSQL's internal [int4] type is [23]. As another
* example, [name_of_type (Int32.of_int 25)] returns ["string"].
*)typeinet=Unix.inet_addr*inttypetimestamptz=Calendar.t*Time_Zone.ttypeint16=inttypebytea=string(* XXX *)typepoint=float*floattypehstore=(string*stringoption)listtypenumeric=stringtypeuuid=stringtypejsonb=stringtypebool_array=booloptionlisttypeint32_array=int32optionlisttypeint64_array=int64optionlisttypestring_array=stringoptionlisttypefloat_array=floatoptionlisttypetimestamp_array=Calendar.toptionlist(** The following conversion functions are used by pa_pgsql to convert
* values in and out of the database.
*)valstring_of_oid:oid->stringvalstring_of_bool:bool->stringvalstring_of_int:int->stringvalstring_of_int16:int16->stringvalstring_of_int32:int32->stringvalstring_of_int64:int64->stringvalstring_of_float:float->stringvalstring_of_point:point->stringvalstring_of_hstore:hstore->stringvalstring_of_numeric:numeric->stringvalstring_of_uuid:uuid->stringvalstring_of_jsonb:jsonb->stringvalstring_of_inet:inet->stringvalstring_of_timestamp:Calendar.t->stringvalstring_of_timestamptz:timestamptz->stringvalstring_of_date:Date.t->stringvalstring_of_time:Time.t->stringvalstring_of_interval:Calendar.Period.t->stringvalstring_of_bytea:bytea->stringvalstring_of_string:string->stringvalstring_of_unit:unit->stringvalstring_of_bool_array:bool_array->stringvalstring_of_int32_array:int32_array->stringvalstring_of_int64_array:int64_array->stringvalstring_of_string_array:string_array->stringvalstring_of_bytea_array:string_array->stringvalstring_of_float_array:float_array->stringvalstring_of_timestamp_array:timestamp_array->stringvalcomment_src_loc:unit->boolvaloid_of_string:string->oidvalbool_of_string:string->boolvalint_of_string:string->intvalint16_of_string:string->int16valint32_of_string:string->int32valint64_of_string:string->int64valfloat_of_string:string->floatvalpoint_of_string:string->pointvalhstore_of_string:string->hstorevalnumeric_of_string:string->numericvaluuid_of_string:string->uuidvaljsonb_of_string:string->jsonbvalinet_of_string:string->inetvaltimestamp_of_string:string->Calendar.tvaltimestamptz_of_string:string->timestamptzvaldate_of_string:string->Date.tvaltime_of_string:string->Time.tvalinterval_of_string:string->Calendar.Period.tvalbytea_of_string:string->byteavalunit_of_string:string->unitvalbool_array_of_string:string->bool_arrayvalint32_array_of_string:string->int32_arrayvalint64_array_of_string:string->int64_arrayvalstring_array_of_string:string->string_arrayvalfloat_array_of_string:string->float_arrayvaltimestamp_array_of_string:string->timestamp_arrayvalbind:'amonad->('a->'bmonad)->'bmonadvalreturn:'a->'amonadendmoduleMake(Thread:THREAD)=structopenThreadtypeconnection_desc={user:string;port:int;password:string;host:[`Hostnameofstring|`Unix_domain_socket_dirofstring];database:string}type'at={ichan:in_channel;(* In_channel wrapping socket. *)chan:out_channel;(* Out_channel wrapping socket. *)mutableprivate_data:'aoption;uuid:string;(* UUID for this connection. *)}type'amonad='aThread.ttypeisolation=[`Serializable|`Repeatable_read|`Read_committed|`Read_uncommitted]typeaccess=[`Read_write|`Read_only]exceptionErrorofstringexceptionPostgreSQL_Errorofstring*(char*string)list(* If true, emit a lot of debugging information about the protocol on stderr.*)letdebug_protocol=false(*----- Code to generate messages for the back-end. -----*)letnew_messagetyp=letbuf=Buffer.create128inbuf,Sometyp(* StartUpMessage and SSLRequest are special messages which don't
* have a type byte field.
*)letnew_start_message()=letbuf=Buffer.create128inbuf,Noneletadd_byte(buf,_)i=(* Deliberately throw an exception if i isn't [0..255]. *)Buffer.add_charbuf(Char.chri)letadd_char(buf,_)c=Buffer.add_charbufcletadd_int16(buf,_)i=ifi<0||i>65_535thenraise(Error"PGOCaml: int16 is outside range [0..65535].");Buffer.add_charbuf(Char.unsafe_chr((ilsr8)land0xff));Buffer.add_charbuf(Char.unsafe_chr(iland0xff))letadd_int32(buf,_)i=letbase=Int32.to_intiinletbig=Int32.to_int(Int32.shift_right_logicali24)inBuffer.add_charbuf(Char.unsafe_chr(bigland0xff));Buffer.add_charbuf(Char.unsafe_chr((baselsr16)land0xff));Buffer.add_charbuf(Char.unsafe_chr((baselsr8)land0xff));Buffer.add_charbuf(Char.unsafe_chr(baseland0xff))letadd_string_no_trailing_nil(buf,_)str=(* Check the string doesn't contain '\0' characters. *)ifString.containsstr'\000'thenraise(Error(sprintf"PGOCaml: string contains ASCII NIL character: %S"str));ifString.lengthstr>0x3fff_ffffthenraise(Error"PGOCaml: string is too long.");Buffer.add_stringbufstrletadd_stringmsgstr=add_string_no_trailing_nilmsgstr;add_bytemsg0letsend_message{chan;_}(buf,typ)=(* Get the length in bytes. *)letlen=4+Buffer.lengthbufin(* If the length is longer than a 31 bit integer, then the message is
* too long to send. This limits messages to 1 GB, which should be
* enough for anyone :-)
*)ifInt64.of_intlen>=0x4000_0000Lthenraise(Error"PGOCaml: message is larger than 1 GB");ifdebug_protocoltheneprintf"> %s%d %S\n%!"(matchtypwith|None->""|Somec->sprintf"%c "c)len(Buffer.contentsbuf);(* Write the type byte? *)(matchtypwith|None->Thread.return()|Somec->output_charchanc)>>=fun()->(* Write the length field. *)output_binary_intchanlen>>=fun()->(* Write the buffer. *)output_stringchan(Buffer.contentsbuf)(* Max message length accepted from back-end. *)letmax_message_length=refSys.max_string_length(* Receive a single result message. Parse out the message type,
* message length, and binary message content.
*)letreceive_message{ichan;chan;_}=(* Flush output buffer. *)flushchan>>=fun()->input_charichan>>=funtyp->input_binary_intichan>>=funlen->(* Discount the length word itself. *)letlen=len-4in(* If the message is too long, give up now. *)iflen>!max_message_lengththen((* Skip the message so we stay in synch with the stream. *)letbufsize=65_536inletbuf=Bytes.createbufsizeinletrecloopn=ifn>0thenbeginletm=minnbufsizeinreally_inputichanbuf0m>>=fun()->loop(n-m)endelsereturn()inlooplen>>=fun()->fail(Error"PGOCaml: back-end message is longer than max_message_length"))else((* Read the binary message content. *)letmsg=Bytes.createleninreally_inputichanmsg0len>>=fun()->return(typ,Bytes.to_stringmsg))(* Send a message and expect a single result. *)letsend_recvconnmsg=send_messageconnmsg>>=fun()->receive_messageconn(* Parse a back-end message. *)typemsg_t=|AuthenticationOk|AuthenticationKerberosV5|AuthenticationCleartextPassword|AuthenticationCryptPasswordofstring|AuthenticationMD5Passwordofstring|AuthenticationSCMCredential|BackendKeyDataofint32*int32|BindComplete|CloseComplete|CommandCompleteofstring|DataRowof(int*string)list|EmptyQueryResponse|ErrorResponseof(char*string)list|NoData|NoticeResponseof(char*string)list|ParameterDescriptionofint32list|ParameterStatusofstring*string|ParseComplete|ReadyForQueryofchar|RowDescriptionof(string*int32*int*int32*int*int32*int)list|UnknownMessageofchar*stringletstring_of_msg_t=function|AuthenticationOk->"AuthenticationOk"|AuthenticationKerberosV5->"AuthenticationKerberosV5"|AuthenticationCleartextPassword->"AuthenticationCleartextPassword"|AuthenticationCryptPasswordstr->sprintf"AuthenticationCleartextPassword %S"str|AuthenticationMD5Passwordstr->sprintf"AuthenticationMD5Password %S"str|AuthenticationSCMCredential->"AuthenticationMD5Password"|BackendKeyData(i1,i2)->sprintf"BackendKeyData %ld, %ld"i1i2|BindComplete->"BindComplete"|CloseComplete->"CloseComplete"|CommandCompletestr->sprintf"CommandComplete %S"str|DataRowfields->sprintf"DataRow [%s]"(String.concat"; "(List.map(fun(len,bytes)->sprintf"%d, %S"lenbytes)fields))|EmptyQueryResponse->"EmptyQueryResponse"|ErrorResponsestrs->sprintf"ErrorResponse [%s]"(String.concat"; "(List.map(fun(k,v)->sprintf"%c, %S"kv)strs))|NoData->"NoData"|NoticeResponsestrs->sprintf"NoticeResponse [%s]"(String.concat"; "(List.map(fun(k,v)->sprintf"%c, %S"kv)strs))|ParameterDescriptionfields->sprintf"ParameterDescription [%s]"(String.concat"; "(List.map(funoid->sprintf"%ld"oid)fields))|ParameterStatus(s1,s2)->sprintf"ParameterStatus %S, %S"s1s2|ParseComplete->"ParseComplete"|ReadyForQueryc->sprintf"ReadyForQuery %s"(matchcwith|'I'->"Idle"|'T'->"inTransaction"|'E'->"Error"|c->sprintf"unknown(%c)"c)|RowDescriptionfields->sprintf"RowDescription [%s]"(String.concat"; "(List.map(fun(name,table,col,oid,len,modifier,format)->sprintf"%s %ld %d %ld %d %ld %d"nametablecoloidlenmodifierformat)fields))|UnknownMessage(typ,msg)->sprintf"UnknownMessage %c, %S"typmsgletparse_backend_message(typ,msg)=letpos=ref0inletlen=String.lengthmsgin(* Functions to grab the next object from the string 'msg'. *)letget_charwhere=if!pos<lenthen(letr=msg.[!pos]inincrpos;r)elseraise(Error("PGOCaml: parse_backend_message: "^where^": short message"))inletget_bytewhere=Char.code(get_charwhere)inletget_int16()=letr0=get_byte"get_int16"inletr1=get_byte"get_int16"in(r0lsr8)+r1inletget_int32()=letr0=get_byte"get_int32"inletr1=get_byte"get_int32"inletr2=get_byte"get_int32"inletr3=get_byte"get_int32"inletr=Int32.of_intr0inletr=Int32.shift_leftr8inletr=Int32.logorr(Int32.of_intr1)inletr=Int32.shift_leftr8inletr=Int32.logorr(Int32.of_intr2)inletr=Int32.shift_leftr8inletr=Int32.logorr(Int32.of_intr3)inrin(*let get_int64 () =
let r0 = get_byte "get_int64" in
let r1 = get_byte "get_int64" in
let r2 = get_byte "get_int64" in
let r3 = get_byte "get_int64" in
let r4 = get_byte "get_int64" in
let r5 = get_byte "get_int64" in
let r6 = get_byte "get_int64" in
let r7 = get_byte "get_int64" in
let r = Int64.of_int r0 in
let r = Int64.shift_left r 8 in
let r = Int64.logor r (Int64.of_int r1) in
let r = Int64.shift_left r 8 in
let r = Int64.logor r (Int64.of_int r2) in
let r = Int64.shift_left r 8 in
let r = Int64.logor r (Int64.of_int r3) in
let r = Int64.shift_left r 8 in
let r = Int64.logor r (Int64.of_int r4) in
let r = Int64.shift_left r 8 in
let r = Int64.logor r (Int64.of_int r5) in
let r = Int64.shift_left r 8 in
let r = Int64.logor r (Int64.of_int r6) in
let r = Int64.shift_left r 8 in
let r = Int64.logor r (Int64.of_int r7) in
r
in*)letget_string()=letbuf=Buffer.create16inletrecloop()=letc=get_char"get_string"inifc<>'\000'then(Buffer.add_charbufc;loop())elseBuffer.contentsbufinloop()inletget_n_bytesn=String.initn(fun_->get_char"get_n_bytes")inletget_char()=get_char"get_char"in(*let get_byte () = get_byte "get_byte" in*)letmsg=matchtypwith|'R'->lett=get_int32()in(matchtwith|0l->AuthenticationOk|2l->AuthenticationKerberosV5|3l->AuthenticationCleartextPassword|4l->letsalt=String.init2(fun_->get_char())inAuthenticationCryptPasswordsalt|5l->letsalt=String.init4(fun_->get_char())inAuthenticationMD5Passwordsalt|6l->AuthenticationSCMCredential|_->UnknownMessage(typ,msg));|'E'->letstrs=ref[]inletrecloop()=letfield_type=get_char()iniffield_type='\000'thenList.rev!strs(* end of list *)else(strs:=(field_type,get_string())::!strs;loop())inErrorResponse(loop())|'N'->letstrs=ref[]inletrecloop()=letfield_type=get_char()iniffield_type='\000'thenList.rev!strs(* end of list *)else(strs:=(field_type,get_string())::!strs;loop())inNoticeResponse(loop())|'Z'->letc=get_char()inReadyForQueryc|'K'->letpid=get_int32()inletkey=get_int32()inBackendKeyData(pid,key)|'S'->letparam=get_string()inletvalue=get_string()inParameterStatus(param,value)|'1'->ParseComplete|'2'->BindComplete|'3'->CloseComplete|'C'->letstr=get_string()inCommandCompletestr|'D'->letnr_fields=get_int16()inletfields=ref[]infor_=0tonr_fields-1doletlen=get_int32()inletfield=iflen<0lthen(-1,"")else(iflen>=0x4000_0000lthenraise(Error"PGOCaml: result field is too long");letlen=Int32.to_intleniniflen>Sys.max_string_lengththenraise(Error"PGOCaml: result field is too wide for string");letbytes=get_n_bytesleninlen,bytes)infields:=field::!fieldsdone;DataRow(List.rev!fields)|'I'->EmptyQueryResponse|'n'->NoData|'T'->letnr_fields=get_int16()inletfields=ref[]infor_=0tonr_fields-1doletname=get_string()inlettable=get_int32()inletcolumn=get_int16()inletoid=get_int32()inletlength=get_int16()inletmodifier=get_int32()inletformat=get_int16()infields:=(name,table,column,oid,length,modifier,format)::!fieldsdone;RowDescription(List.rev!fields)|'t'->letnr_fields=get_int16()inletfields=ref[]infor_=0tonr_fields-1doletoid=get_int32()infields:=oid::!fieldsdone;ParameterDescription(List.rev!fields)|_->UnknownMessage(typ,msg)inifdebug_protocoltheneprintf"< %s\n%!"(string_of_msg_tmsg);msgletverbose=ref1typeseverity=ERROR|FATAL|PANIC|WARNING|NOTICE|DEBUG|INFO|LOGletget_severityfields=letfield=tryList.assoc'V'fields(* introduced with PostgreSQL 9.6 *)withNot_found->List.assoc'S'fieldsinmatchfieldwith|"ERROR"->ERROR|"FATAL"->FATAL|"PANIC"->PANIC|"WARNING"->WARNING|"NOTICE"->NOTICE|"DEBUG"->DEBUG|"INFO"->INFO|"LOG"->LOG|_->raiseNot_foundletshow_severity=function|ERROR->"ERROR"|FATAL->"FATAL"|PANIC->"PANIC"|WARNING->"WARNING"|NOTICE->"NOTICE"|DEBUG->"DEBUG"|INFO->"INFO"|LOG->"LOG"(* Print an ErrorResponse on stderr. *)letprint_ErrorResponsefields=if!verbose>=1then(tryletseverity=trySome(get_severityfields)withNot_found->Noneinletseverity_string=matchseveritywith|Somes->show_severitys|None->"UNKNOWN"inletcode=List.assoc'C'fieldsinletmessage=List.assoc'M'fieldsinif!verbose=1thenmatchseveritywith|SomeERROR|SomeFATAL|SomePANIC->eprintf"%s: %s: %s\n%!"severity_stringcodemessage|_->()elseeprintf"%s: %s: %s\n%!"severity_stringcodemessagewithNot_found->eprintf"WARNING: 'Always present' field is missing in error message\n%!");if!verbose>=2then(List.iter(fun(field_type,field)->iffield_type<>'S'&&field_type<>'C'&&field_type<>'M'theneprintf"%c: %s\n%!"field_typefield)fields)letsync_msgconn=letmsg=new_message'S'insend_messageconnmsg(* Handle an ErrorResponse anywhere, by printing and raising an exception. *)letpg_error?connfields=print_ErrorResponsefields;letstr=tryletseverity_string=tryshow_severity@@get_severityfieldswithNot_found->"UNKNOWN"inletcode=List.assoc'C'fieldsinletmessage=List.assoc'M'fieldsinsprintf"%s: %s: %s"severity_stringcodemessagewithNot_found->"WARNING: 'Always present' field is missing in error message"in(* If conn parameter was given, then resynch - read messages until we
* see ReadyForQuery.
*)(matchconnwith|None->return()|Someconn->letrecloop()=receive_messageconn>>=funmsg->letmsg=parse_backend_messagemsginmatchmsgwithReadyForQuery_->return()|_->loop()inloop())>>=fun()->fail(PostgreSQL_Error(str,fields))(*----- Profiling. -----*)type'aretexn=Retof'a|Exnofexn(* profile_op :
* string -> string -> string list -> (unit -> 'a Thread.t) -> 'a Thread.t
*)letprofile_opuuidopdetailf=letchan=tryletfilename=Sys.getenv"PGPROFILING"inletflags=[Open_wronly;Open_append;Open_creat]inletchan=open_out_genflags0o644filenameinSomechanwith|Not_found|Sys_error_->Noneinmatchchanwith|None->f()(* No profiling - just run it. *)|Somechan->(* Profiling. *)letstart_time=Unix.gettimeofday()incatch(fun()->f()>>=funx->return(Retx))(funexn->return(Exnexn))>>=funret->letend_time=Unix.gettimeofday()inletelapsed_time_ms=int_of_float(1000.*.(end_time-.start_time))inletrow=["1";(* Version number. *)uuid;op;string_of_intelapsed_time_ms;matchretwith|Ret_->"ok"|Exnexn->Printexc.to_stringexn]@detailin(* Lock the output channel while we write the row, to prevent
* corruption from multiple writers.
*)letfd=Unix.descr_of_out_channelchaninUnix.lockffdUnix.F_LOCK0;Csv.output_all(Csv.to_channelchan)[row];close_outchan;(* Return result or re-raise the exception. *)matchretwith|Retr->returnr|Exnexn->failexn(*----- Connection. -----*)letpgsql_socketdirport=letsockaddr=sprintf"%s/.s.PGSQL.%d"dirportinUnix.ADDR_UNIXsockaddrletdescribe_connection?host?port?user?password?database?(unix_domain_socket_dir)()=(* Get the username. *)letuser=matchuserwith|Someuser->user|None->trySys.getenv"PGUSER"withNot_found->tryletpw=Unix.getpwuid(Unix.geteuid())inpw.Unix.pw_namewithNot_found->PGOCaml_config.default_userin(* Get the password. *)letpassword=matchpasswordwith|Somepassword->password|None->trySys.getenv"PGPASSWORD"withNot_found->PGOCaml_config.default_passwordin(* Get the database name. *)letdatabase=matchdatabasewith|Somedatabase->database|None->trySys.getenv"PGDATABASE"withNot_found->userin(* Hostname and port number. *)lethost=match(host,unix_domain_socket_dir)with|(Some_),(Some_)->raise(Failure"describe_connection: it's invalid to specify both a HOST and a unix domain socket directory")|(Somes),NonewhenString.lengths>0&&String.gets0='/'->`Unix_domain_socket_dirs|(Somes),None->`Hostnames|None,(Somes)->`Unix_domain_socket_dirs|None,None->try`Hostname(Sys.getenv"PGHOST")withNot_found->(* fall back on Unix domain socket. *)`Unix_domain_socket_dirPGOCaml_config.default_unix_domain_socket_dirinletport=matchportwith|Someport->port|None->tryint_of_string(Sys.getenv"PGPORT")withNot_found|Failure_->PGOCaml_config.default_portin{user;host;port;database;password}(** We need to convert keys to a human-readable format for error reporting.
*)letconnection_desc_to_stringkey=Printf.sprintf"host=%s, port=%s, user=%s, password=%s, database=%s"(matchkey.hostwith`Unix_domain_socket_dir_->"unix"|`Hostnames->s)(string_of_intkey.port)key.user"*****"(* we don't want to be dumping passwords into error logs *)key.databaseletconnect?host?port?user?password?database?unix_domain_socket_dir?desc()=let{user;host;port;database;password}=matchdescwith|None->describe_connection?host?port?user?password?database?unix_domain_socket_dir()|Somedesc->descin(* Make the socket address. *)letsockaddrs=matchhostwith|`Hostnamehostname->letaddrs=Unix.getaddrinfohostname(sprintf"%d"port)[Unix.AI_SOCKTYPE(Unix.SOCK_STREAM)]inifaddrs=[]thenraise(Error("PGOCaml: unknown host: "^hostname))elseList.map(fun{Unix.ai_addr=sockaddr;_}->sockaddr)addrs|`Unix_domain_socket_dirudsd->(* Unix domain socket. *)[pgsql_socketudsdport]in(* Create a universally unique identifier for this connection. This
* is mainly for debugging and profiling.
*)letuuid=(*
* On Windows, the result of Unix.getpid is largely meaningless (it's not unique)
* and, more importantly, Unix.getppid is not implemented.
*)letppid=tryUnix.getppid()withInvalid_argument_->0insprintf"%s %d %d %g %s %g"(Unix.gethostname())(Unix.getpid())ppid(Unix.gettimeofday())Sys.executable_name((Unix.times()).Unix.tms_utime)inletuuid=Digest.to_hex(Digest.stringuuid)inletsock_channels=letreccreate_sock_channelssockaddrs=matchsockaddrswith[]->raise(Error("PGOCaml: Could not connect to database"))|sockaddr::sockaddrs->catch(fun()->open_connectionsockaddr)(function|Unix.Unix_error_->create_sock_channelssockaddrs|exn->raiseexn)increate_sock_channelssockaddrsinletdo_connect()=sock_channels>>=fun(ichan,chan)->catch(fun()->(* Create the connection structure. *)letconn={ichan=ichan;chan=chan;private_data=None;uuid=uuid}in(* Send the StartUpMessage. NB. At present we do not support SSL. *)letmsg=new_start_message()inadd_int32msg196608l;add_stringmsg"user";add_stringmsguser;add_stringmsg"database";add_stringmsgdatabase;add_bytemsg0;(* Loop around here until the database gives a ReadyForQuery message. *)letrecloopmsg=(matchmsgwith|Somemsg->send_recvconnmsg|None->receive_messageconn)>>=funmsg->letmsg=parse_backend_messagemsginmatchmsgwith|ReadyForQuery_->return()(* Finished connecting! *)|BackendKeyData_->(* XXX We should save this key. *)loopNone|ParameterStatus_->(* Should we do something with this? *)loopNone|AuthenticationOk->loopNone|AuthenticationKerberosV5->fail(Error"PGOCaml: Kerberos authentication not supported")|AuthenticationCleartextPassword->letmsg=new_message'p'in(* PasswordMessage *)add_stringmsgpassword;loop(Somemsg)|AuthenticationCryptPassword_salt->(* Crypt password not supported because there is no crypt(3) function
* in OCaml.
*)fail(Error"PGOCaml: crypt password authentication not supported")|AuthenticationMD5Passwordsalt->(* (* This is a guess at how the salt is used ... *)
let password = salt ^ password in
let password = Digest.string password in*)letpassword="md5"^Digest.to_hex(Digest.string(Digest.to_hex(Digest.string(password^user))^salt))inletmsg=new_message'p'in(* PasswordMessage *)add_stringmsgpassword;loop(Somemsg)|AuthenticationSCMCredential->fail(Error"PGOCaml: SCM Credential authentication not supported")|ErrorResponseerr->pg_errorerr|NoticeResponse_err->(* XXX Do or print something here? *)loopNone|_->(* Silently ignore unknown or unexpected message types. *)loopNoneinloop(Somemsg)>>=fun()->returnconn)(fune->close_inichan>>=fun()->faile)inletdetail=["user";user;"database";database;"host";beginmatchhostwith`Unix_domain_socket_dir_->"unix"|`Hostnames->send;"port";string_of_intport;"prog";Sys.executable_name]inprofile_opuuid"connect"detaildo_connectletcloseconn=letdo_close()=catch(fun()->(* Be nice and send the terminate message. *)letmsg=new_message'X'insend_messageconnmsg>>=fun()->flushconn.chan>>=fun()->returnNone)(fune->return(Somee))>>=fune->(* Closes the underlying socket too. *)close_inconn.ichan>>=fun()->matchewith|None->return()|Somee->faileinprofile_opconn.uuid"close"[]do_closeletset_private_dataconndata=conn.private_data<-Somedataletprivate_data{private_data;_}=matchprivate_datawith|None->raiseNot_found|Someprivate_data->private_dataletuuidconn=conn.uuidtypepa_pg_data=(string,bool)Hashtbl.tletpingconn=letdo_ping()=sync_msgconn>>=fun()->(* Wait for ReadyForQuery. *)letrecloop()=receive_messageconn>>=funmsg->letmsg=parse_backend_messagemsginmatchmsgwith|ReadyForQuery_->return()(* Finished! *)|ErrorResponseerr->pg_error~connerr(* Error *)|_->loop()inloop()inprofile_opconn.uuid"ping"[]do_pingletaliveconn=catch(fun()->pingconn>>=fun()->returntrue)(fun_->returnfalse)typeoid=int32typeparam=stringoptiontyperesult=stringoptiontyperow=resultlistletprepareconn~query?(name="")?(types=[])()=letdo_prepare()=letmsg=new_message'P'inadd_stringmsgname;add_stringmsgquery;add_int16msg(List.lengthtypes);List.iter(add_int32msg)types;send_messageconnmsg>>=fun()->sync_msgconn>>=fun()->letrecloop()=receive_messageconn>>=funmsg->letmsg=parse_backend_messagemsginmatchmsgwith|ErrorResponseerr->pg_error~connerr|ParseComplete->loop()|ReadyForQuery_->return()(* Finished! *)|NoticeResponse_->(* XXX Do or print something here? *)loop()|_->fail(Error("PGOCaml: unknown response from parse: "^string_of_msg_tmsg))inloop()inletdetails=["query";query;"name";name]inprofile_opconn.uuid"prepare"detailsdo_prepareletiter_executeconnnameportalparamsproc()=(* Bind *)letmsg=new_message'B'inadd_stringmsgportal;add_stringmsgname;add_int16msg0;(* Send all parameters as text. *)add_int16msg(List.lengthparams);List.iter(funparam->matchparamwith|None->add_int32msg0xffff_ffffl(* NULL *)|Somestr->add_int32msg(Int32.of_int(String.lengthstr));add_string_no_trailing_nilmsgstr)params;add_int16msg0;(* Send back all results as text. *)send_messageconnmsg>>=fun()->(* Execute *)letmsg=new_message'E'inadd_stringmsgportal;add_int32msg0l;(* no limit on rows *)send_messageconnmsg>>=fun()->(* Sync *)sync_msgconn>>=fun()->(* Process the message(s) received from the database until we read
* ReadyForQuery. In the process we may get some rows back from
* the database, no data, or an error.
*)letrecloop()=(* NB: receive_message flushes the output connection. *)receive_messageconn>>=funmsg->letmsg=parse_backend_messagemsginmatchmsgwith|ReadyForQuery_->return()(* Finished! *)|ErrorResponseerr->pg_error~connerr(* Error *)|NoticeResponse_err->(* XXX Do or print something here? *)loop()|BindComplete->loop()|CommandComplete_->loop()|EmptyQueryResponse->loop()|DataRowfields->letfields=List.map(function|(i,_)wheni<0->None(* NULL *)|(0,_)->Some""|(_,bytes)->Somebytes)fieldsinprocfields>>=loop|NoData->loop()|ParameterStatus_->(* 43.2.6: ParameterStatus messages will be generated whenever
* the active value changes for any of the parameters the backend
* believes the frontend should know about. Most commonly this
* occurs in response to a SET SQL command executed by the
* frontend, and this case is effectively synchronous -- but it
* is also possible for parameter status changes to occur because
* the administrator changed a configuration file and then sent
* the SIGHUP signal to the postmaster.
*)loop()|_->fail(Error("PGOCaml: unknown response message: "^string_of_msg_tmsg))inloop()letdo_executeconnnameportalparamsrev()=letrows=ref[]initer_executeconnnameportalparams(funfields->return(rows:=fields::!rows))()>>=fun()->(* Return the result rows. *)return(ifrevthenList.rev!rowselse!rows)letexecute_revconn?(name="")?(portal="")~params()=letdo_execute=do_executeconnnameportalparamsfalseinletdetails=["name";name;"portal";portal]inprofile_opconn.uuid"execute"detailsdo_executeletexecuteconn?(name="")?(portal="")~params()=letdo_execute=do_executeconnnameportalparamstrueinletdetails=["name";name;"portal";portal]inprofile_opconn.uuid"execute"detailsdo_executeletcursorconn?(name="")?(portal="")~paramsproc=letdo_execute=iter_executeconnnameportalparamsprocinletdetails=["name";name;"portal";portal]inprofile_opconn.uuid"cursor"detailsdo_executeletbegin_work?isolation?access?deferrableconn=letisolation_str=matchisolationwith|None->""|Somex->" isolation level "^(matchxwith|`Serializable->"serializable"|`Repeatable_read->"repeatable read"|`Read_committed->"read committed"|`Read_uncommitted->"read uncommitted")inletaccess_str=matchaccesswith|None->""|Somex->matchxwith|`Read_write->" read write"|`Read_only->" read only"inletdeferrable_str=matchdeferrablewith|None->""|Somex->(matchxwithtrue->""|false->" not")^" deferrable"inletquery="begin work"^isolation_str^access_str^deferrable_strinprepareconn~query()>>=fun()->executeconn~params:[]()>>=fun_->return()letcommitconn=letquery="commit"inprepareconn~query()>>=fun()->executeconn~params:[]()>>=fun_->return()letrollbackconn=letquery="rollback"inprepareconn~query()>>=fun()->executeconn~params:[]()>>=fun_->return()lettransactconn?isolation?access?deferrablef=begin_work?isolation?access?deferrableconn>>=fun()->catch(fun()->fconn>>=funr->commitconn>>=fun()->returnr)(fune->rollbackconn>>=fun()->faile)letserialconnname=letquery="select currval ($1)"inprepareconn~query()>>=fun()->executeconn~params:[Somename]()>>=funrows->letrow=List.hdrowsinletresult=List.hdrowin(* NB. According to the manual, the return type of currval is
* always a bigint, whether or not the column is serial or bigserial.
*)return(Int64.of_string(Option.getresult))letserial4connname=serialconnname>>=funs->return(Int64.to_int32s)letserial8=serialletclose_statementconn?(name="")()=letmsg=new_message'C'inadd_charmsg'S';add_stringmsgname;send_messageconnmsg>>=fun()->sync_msgconn>>=fun()->letrecloop()=receive_messageconn>>=funmsg->letmsg=parse_backend_messagemsginmatchmsgwith|ErrorResponseerr->pg_error~connerr|CloseComplete->loop()|ReadyForQuery_->return()(* Finished! *)|NoticeResponse_->(* XXX Do or print something here? *)loop()|_->fail(Error("PGOCaml: unknown response from close: "^string_of_msg_tmsg))inloop()letclose_portalconn?(portal="")()=letmsg=new_message'C'inadd_charmsg'P';add_stringmsgportal;send_messageconnmsg>>=fun()->sync_msgconn>>=fun()->letrecloop()=receive_messageconn>>=funmsg->letmsg=parse_backend_messagemsginmatchmsgwith|ErrorResponseerr->pg_error~connerr|CloseComplete->loop()|ReadyForQuery_->return()(* Finished! *)|NoticeResponse_->(* XXX Do or print something here? *)loop()|_->fail(Error("PGOCaml: unknown response from close: "^string_of_msg_tmsg))inloop()letinjectdb?namequery=preparedb~query?name()>>=fun()->executedb?name~params:[]()>>=funret->close_statementdb?name()>>=fun()->returnretletalterdb?namequery=injectdb?namequery>>=fun_->return()typeresult_description={name:string;table:oidoption;column:intoption;field_type:oid;length:int;modifier:int32;}typerow_description=result_descriptionlisttypeparam_description={param_type:oid;}typeparams_description=param_descriptionlistletexpect_rfqconnret=receive_messageconn>>=funmsg->matchparse_backend_messagemsgwith|ReadyForQuery_->returnret|msg->fail@@Error("PGOCaml: unknown response from describe: "^string_of_msg_tmsg)letdescribe_statementconn?(name="")()=letmsg=new_message'D'inadd_charmsg'S';add_stringmsgname;send_messageconnmsg>>=fun()->sync_msgconn>>=fun()->receive_messageconn>>=funmsg->letmsg=parse_backend_messagemsgin(matchmsgwith|ErrorResponseerr->pg_error~connerr|ParameterDescriptionparams->letparams=List.map(funoid->{param_type=oid})paramsinreturnparams|_->fail(Error("PGOCaml: unknown response from describe: "^string_of_msg_tmsg)))>>=funparams->receive_messageconn>>=funmsg->letmsg=parse_backend_messagemsgin(matchmsgwith|ErrorResponseerr->pg_error~connerr|NoData->return(params,None)|RowDescriptionfields->letfields=List.map(fun(name,table,column,oid,length,modifier,_)->{name=name;table=iftable=0lthenNoneelseSometable;column=ifcolumn=0thenNoneelseSomecolumn;field_type=oid;length=length;modifier=modifier;})fieldsinreturn(params,Somefields)|_->fail(Error("PGOCaml: unknown response from describe: "^string_of_msg_tmsg)))>>=expect_rfqconnletdescribe_portalconn?(portal="")()=letmsg=new_message'D'inadd_charmsg'P';add_stringmsgportal;send_messageconnmsg>>=fun()->sync_msgconn>>=fun()->receive_messageconn>>=funmsg->letmsg=parse_backend_messagemsgin(matchmsgwith|ErrorResponseerr->pg_error~connerr|NoData->returnNone|RowDescriptionfields->letfields=List.map(fun(name,table,column,oid,length,modifier,_)->{name=name;table=iftable=0lthenNoneelseSometable;column=ifcolumn=0thenNoneelseSomecolumn;field_type=oid;length=length;modifier=modifier;})fieldsinreturn(Somefields)|_->fail(Error("PGOCaml: unknown response from describe: "^string_of_msg_tmsg)))>>=expect_rfqconn(*----- Type conversion. -----*)(* For certain types, more information is available by looking
* at the modifier field as well as just the OID. For example,
* for NUMERIC the modifier tells us the precision.
* However we don't always have the modifier field available -
* in particular for parameters.
*)letname_of_type=function|16_l->"bool"(* BOOLEAN *)|17_l->"bytea"(* BYTEA *)|20_l->"int64"(* INT8 *)|21_l->"int16"(* INT2 *)|23_l->"int32"(* INT4 *)|25_l->"string"(* TEXT *)|114_l->"string"(* JSON *)|119_l->"string_array"(* JSON[] *)|600_l->"point"(* POINT *)|700_l|701_l->"float"(* FLOAT4, FLOAT8 *)|869_l->"inet"(* INET *)|1000_l->"bool_array"(* BOOLEAN[] *)|1001_l->"bytea_array"(* BYTEA[] *)|1007_l->"int32_array"(* INT4[] *)|1009_l->"string_array"(* TEXT[] *)|1014_l->"string_array"(* CHAR[] *)|1015_l->"string_array"(* VARCHAR[] *)|1016_l->"int64_array"(* INT8[] *)|1021_l|1022_l->"float_array"(* FLOAT4[], FLOAT8[] *)|1042_l->"string"(* CHAR(n) - treat as string *)|1043_l->"string"(* VARCHAR(n) - treat as string *)|1082_l->"date"(* DATE *)|1083_l->"time"(* TIME *)|1114_l->"timestamp"(* TIMESTAMP *)|1115_l->"timestamp_array"(* TIMESTAMP[] *)|1184_l->"timestamptz"(* TIMESTAMP WITH TIME ZONE *)|1186_l->"interval"(* INTERVAL *)|2278_l->"unit"(* VOID *)|1700_l->"string"(* NUMERIC *)|2950_l->"uuid"(* UUID *)|3802_l->"string"(* JSONB *)|3807_l->"string_array"(* JSONB[] *)|i->(* For unknown types, look at <postgresql/catalog/pg_type.h>. *)raise(Error("PGOCaml: unknown type for OID "^Int32.to_stringi))typeinet=Unix.inet_addr*inttypetimestamptz=Calendar.t*Time_Zone.ttypeint16=inttypebytea=stringtypepoint=float*floattypehstore=(string*stringoption)listtypenumeric=stringtypeuuid=stringtypejsonb=stringtypebool_array=booloptionlisttypeint32_array=int32optionlisttypeint64_array=int64optionlisttypestring_array=stringoptionlisttypefloat_array=floatoptionlisttypetimestamp_array=Calendar.toptionlistletstring_of_hstorehstore=letstring_of_quotedstr="\""^str^"\""inletstring_of_mapping(key,value)=letkey_str=string_of_quotedkeyinletvalue_str=matchvaluewith|Somev->string_of_quotedv|None->"NULL"inkey_str^"=>"^value_strinString.join", "(List.mapstring_of_mappinghstore)letstring_of_numeric(x:string)=xletstring_of_uuid(x:string)=xletstring_of_jsonb(x:string)=xletstring_of_inet(addr,mask)=lethostmask=ifUnix.domain_of_sockaddr(Unix.ADDR_INET(addr,1))=Unix.PF_INET6then128else32inletaddr=Unix.string_of_inet_addraddrinifmask=hostmaskthenaddrelseifmask>=0&&mask<hostmaskthenaddr^"/"^string_of_intmaskelsefailwith"string_of_inet"letstring_of_oid=Int32.to_stringletstring_of_bool=function|true->"t"|false->"f"letstring_of_int=Stdlib.string_of_intletstring_of_int16=Stdlib.string_of_intletstring_of_int32=Int32.to_stringletstring_of_int64=Int64.to_stringletstring_of_float=string_of_floatletstring_of_point(x,y)="("^(string_of_floatx)^","^(string_of_floaty)^")"letstring_of_timestamp=Printer.Calendar.to_stringletstring_of_timestamptz(cal,tz)=Printer.Calendar.to_stringcal^matchtzwith|Time_Zone.UTC->"+00"|Time_Zone.Local->letgap=Time_Zone.gapTime_Zone.UTCTime_Zone.Localinifgap>=0thensprintf"+%02d"gapelsesprintf"-%02d"(-gap)|Time_Zone.UTC_Plusgap->ifgap>=0thensprintf"+%02d"gapelsesprintf"-%02d"(-gap)letstring_of_date=Printer.Date.to_stringletstring_of_time=Printer.Time.to_stringletstring_of_intervalp=lety,m,d,s=Calendar.Period.ymdspinsprintf"%d years %d mons %d days %d seconds"ymdsletstring_of_unit()=""(* NB. It is the responsibility of the caller of this function to
* properly escape array elements.
*)letstring_of_any_arrayxs=letbuf=Buffer.create128inBuffer.add_charbuf'{';letadderix=ifi>0thenBuffer.add_charbuf',';matchxwith|Somex->Buffer.add_charbuf'"';Buffer.add_stringbufx;Buffer.add_charbuf'"'|None->Buffer.add_stringbuf"NULL"inList.iteriadderxs;Buffer.add_charbuf'}';Buffer.contentsbufletoption_mapf=function|Somex->Some(fx)|None->Noneletescape_stringstr=letbuf=Buffer.create128infori=0toString.lengthstr-1domatchstr.[i]with|'"'|'\\'asx->Buffer.add_charbuf'\\';Buffer.add_charbufx|x->Buffer.add_charbufxdone;Buffer.contentsbufletstring_of_bool_arraya=string_of_any_array(List.map(option_mapstring_of_bool)a)letstring_of_int32_arraya=string_of_any_array(List.map(option_mapInt32.to_string)a)letstring_of_int64_arraya=string_of_any_array(List.map(option_mapInt64.to_string)a)letstring_of_string_arraya=string_of_any_array(List.map(option_mapescape_string)a)letstring_of_float_arraya=string_of_any_array(List.map(option_mapstring_of_float)a)letstring_of_timestamp_arraya=string_of_any_array(List.map(option_mapstring_of_timestamp)a)letcomment_src_loc()=matchSys.getenv_opt"PGCOMMENT_SRC_LOC"with|Somex->beginmatchxwith|"yes"|"1"|"on"->true|"no"|"0"|"off"->false|_->failwith(Printf.sprintf"Unrecognized option for 'PGCOMMENT_SRC_LOC': %s"x)end|None->PGOCaml_config.default_comment_src_locletstring_of_byteab=let`Hexb_hex=Hex.of_stringbin"\\x"^b_hexletstring_of_bytea_arraya=string_of_any_array(List.map(option_mapstring_of_bytea)a)letstring_of_string(x:string)=xletoid_of_string=Int32.of_stringletbool_of_string=function|"true"|"t"->true|"false"|"f"->false|str->raise(Error("PGOCaml: not a boolean: "^str))letint_of_string=Stdlib.int_of_stringletint16_of_string=Stdlib.int_of_stringletint32_of_string=Int32.of_stringletint64_of_string=Int64.of_stringletfloat_of_string=float_of_stringlethstore_of_stringstr=letexpecttargetstream=ifList.exists(func->c<>Stream.nextstream)targetthenraise(Error("PGOCaml: unexpected input in hstore_of_string"))inletparse_quotedstream=letrecloopaccumstream=matchStream.nextstreamwith|'"'->String.implode(List.revaccum)|'\\'->loop(Stream.nextstream::accum)stream|x->loop(x::accum)streaminexpect['"']stream;loop[]streaminletparse_valuestream=matchStream.peekstreamwith|Some'N'->(expect['N';'U';'L';'L']stream;None)|_->Some(parse_quotedstream)inletparse_mappingstream=letkey=parse_quotedstreaminexpect['=';'>']stream;letvalue=parse_valuestreamin(key,value)inletparse_mainstream=letrecloopaccumstream=letmapping=parse_mappingstreaminmatchStream.peekstreamwith|Some_->(expect[',';' ']stream;loop(mapping::accum)stream)|None->mapping::accuminmatchStream.peekstreamwith|Some_->loop[]stream|None->[]inparse_main(Stream.of_stringstr)letnumeric_of_string(x:string)=xletuuid_of_string(x:string)=xletjsonb_of_string(x:string)=xletinet_of_string=letre=letopenRein[group([rep(compl[set":./"]);group(set":.");rep1(compl[char'/'])]|>seq);opt(seq[char'/';group(rep1any)])]|>seq|>compileinfunstr->letsubs=Re.execrestrinletaddr=Unix.inet_addr_of_string(Re.Group.getsubs1)inletmask=try(Re.Group.getsubs3)withNot_found->""in(* optional match *)ifmask=""then(addr,(if(Re.Group.getsubs2)="."then32else128))else(addr,int_of_stringmask)letpoint_of_string=letpoint_re=letopenReinletspacep=letspace=rep(set" \t")inseq[space;p;space]inletsign=opt(set"+-")inletnum=seq[sign;rep1digit;opt(char'.');repdigit]inletnan=seq[set"Nn";char'a';set"Nn"]inletinf=seq[sign;set"Ii";str"nfinity"]inletfloat_pat=Re.alt[num;nan;inf]in[char'(';space(groupfloat_pat);char',';space(groupfloat_pat);char')']|>seq|>compileinfunstr->tryletsubs=Re.execpoint_restrin(float_of_string(Re.Group.getsubs1),float_of_string(Re.Group.getsubs2))with|_->failwith"point_of_string"letdate_of_string=Printer.Date.from_stringlettime_of_stringstr=(* Remove trailing ".microsecs" if present. *)letn=String.lengthstrinletstr=ifn>8&&str.[8]='.'thenString.substr08elsestrinPrinter.Time.from_stringstrlettimestamp_of_stringstr=(* Remove trailing ".microsecs" if present. *)letn=String.lengthstrinletstr=ifn>19&&str.[19]='.'thenString.substr019elsestrinPrinter.Calendar.from_stringstrlettimestamptz_of_stringstr=(* Split into datetime+timestamp. *)letn=String.lengthstrinletcal,tz=ifn>=3&&(str.[n-3]='+'||str.[n-3]='-')thenString.substr0(n-3),Some(String.substr(n-3)3)elsestr,Noneinletcal=timestamp_of_stringcalinlettz=matchtzwith|None->Time_Zone.Local(* best guess? *)|Sometz->letsgn=matchtz.[0]with'+'->1|'-'->-1|_->assertfalseinletmag=int_of_string(String.subtz12)inTime_Zone.UTC_Plus(sgn*mag)incal,tzletre_interval=letopenReinlettime_periodunit_name=[group(rep1digit);space;strunit_name;opt(char's')]|>seq|>optinletdigit2=[digit;digit]|>seq|>groupinlettime=seq[digit2;char':';digit2;opt(seq[char':';digit2])]in[opt(time_period"year");repspace;opt(time_period"mon");repspace;opt(time_period"day");repspace;opttime]|>seq|>compileletinterval_of_string=letint_optsubsi=tryint_of_string(Re.Group.getsubsi)with|Not_found->0infunstr->tryletsub=Re.execre_intervalstrinCalendar.Period.make(int_optsub1)(* year *)(int_optsub2)(* month *)(int_optsub3)(* day *)(int_optsub4)(* hour *)(int_optsub5)(* min *)(int_optsub6)(* sec *)withNot_found->failwith("interval_of_string: bad interval: "^str)letunit_of_string_=()(* NB. This function also takes care of unescaping returned elements. *)letany_array_of_stringstr=letn=String.lengthstrinassert(str.[0]='{');assert(str.[n-1]='}');letstr=String.substr1(n-2)inletbuf=Buffer.create128inletadd_fieldaccum=letx=Buffer.contentsbufinBuffer.clearbuf;letfield=ifx="NULL"thenNoneelseletn=String.lengthxinifn>=2&&x.[0]='"'thenSome(String.subx1(n-2))elseSomexinfield::accuminletloop(accum,quoted,escaped)=function|'\\'whennotescaped->(accum,quoted,true)|'"'whennotescaped->Buffer.add_charbuf'"';(accum,notquoted,false)|','whennotescaped&¬quoted->(add_fieldaccum,false,false)|x->Buffer.add_charbufx;(accum,quoted,false)inlet(accum,_,_)=String.fold_leftloop([],false,false)strinletaccum=ifBuffer.lengthbuf=0thenaccumelseadd_fieldaccuminList.revaccumletbool_array_of_stringstr=List.map(option_mapbool_of_string)(any_array_of_stringstr)letint32_array_of_stringstr=List.map(option_mapInt32.of_string)(any_array_of_stringstr)letint64_array_of_stringstr=List.map(option_mapInt64.of_string)(any_array_of_stringstr)letstring_array_of_stringstr=any_array_of_stringstrletfloat_array_of_stringstr=List.map(option_mapfloat_of_string)(any_array_of_stringstr)lettimestamp_array_of_stringstr=List.map(option_maptimestamp_of_string)(any_array_of_stringstr)letis_first_oct_digitc=c>='0'&&c<='3'letis_oct_digitc=c>='0'&&c<='7'letoct_valc=Char.codec-0x30letis_hex_digit=function'0'..'9'|'a'..'f'|'A'..'F'->true|_->falselethex_valc=letoffset=matchcwith|'0'..'9'->0x30|'a'..'f'->0x57|'A'..'F'->0x37|_->failwith"hex_val"inChar.codec-offset(* Deserialiser for the new 'hex' format introduced in PostgreSQL 9.0. *)letbytea_of_string_hexstr=letlen=String.lengthstrinletbuf=Buffer.create((len-2)/2)inleti=ref3inwhile!i<lendolethi_nibble=str.[!i-1]inletlo_nibble=str.[!i]ini:=!i+2;ifis_hex_digithi_nibble&&is_hex_digitlo_nibblethenbeginletbyte=((hex_valhi_nibble)lsl4)+(hex_vallo_nibble)inBuffer.add_charbuf(Char.chrbyte)enddone;Buffer.contentsbuf(* Deserialiser for the old 'escape' format used in PostgreSQL < 9.0. *)letbytea_of_string_escapestr=letlen=String.lengthstrinletbuf=Buffer.createleninleti=ref0inwhile!i<lendoletc=str.[!i]inifc='\\'then(incri;if!i<len&&str.[!i]='\\'then(Buffer.add_charbuf'\\';incri)elseif!i+2<len&&is_first_oct_digitstr.[!i]&&is_oct_digitstr.[!i+1]&&is_oct_digitstr.[!i+2]then(letbyte=oct_valstr.[!i]inincri;letbyte=(bytelsl3)+oct_valstr.[!i]inincri;letbyte=(bytelsl3)+oct_valstr.[!i]inincri;Buffer.add_charbuf(Char.chrbyte)))else(incri;Buffer.add_charbufc)done;Buffer.contentsbuf(* PostgreSQL 9.0 introduced the new 'hex' format for binary data.
We must therefore check whether the data begins with a magic sequence
that identifies this new format and if so call the appropriate parser;
if it doesn't, then we invoke the parser for the old 'escape' format.
*)letbytea_of_stringstr=ifString.starts_withstr"\\x"thenbytea_of_string_hexstrelsebytea_of_string_escapestrletbind=(>>=)letreturn=Thread.returnend