commit 08ba30e8f933711e8f623ced502d4c3ad53f97f5 Author: pgagarinov Date: Fri Jan 22 14:27:33 2016 +0300 Initial commit: Version 1.5.2a of libpqtypes which contains a few critical bug fixes on top of official version 1.5.1 diff --git a/bin/glnxa64/libpqtypes.so b/bin/glnxa64/libpqtypes.so new file mode 100644 index 0000000..e32763c Binary files /dev/null and b/bin/glnxa64/libpqtypes.so differ diff --git a/bin/glnxa64/libpqtypes.so.0 b/bin/glnxa64/libpqtypes.so.0 new file mode 100644 index 0000000..e32763c Binary files /dev/null and b/bin/glnxa64/libpqtypes.so.0 differ diff --git a/bin/win32/libpqtypes.dll b/bin/win32/libpqtypes.dll new file mode 100644 index 0000000..9ac77dd Binary files /dev/null and b/bin/win32/libpqtypes.dll differ diff --git a/bin/win32/libpqtypes.lib b/bin/win32/libpqtypes.lib new file mode 100644 index 0000000..5b0f17e Binary files /dev/null and b/bin/win32/libpqtypes.lib differ diff --git a/bin/win32/libpqtypesdll.lib b/bin/win32/libpqtypesdll.lib new file mode 100644 index 0000000..81a9800 Binary files /dev/null and b/bin/win32/libpqtypesdll.lib differ diff --git a/bin/win64/libpqtypes.dll b/bin/win64/libpqtypes.dll new file mode 100644 index 0000000..e58d967 Binary files /dev/null and b/bin/win64/libpqtypes.dll differ diff --git a/bin/win64/libpqtypes.lib b/bin/win64/libpqtypes.lib new file mode 100644 index 0000000..987823f Binary files /dev/null and b/bin/win64/libpqtypes.lib differ diff --git a/bin/win64/libpqtypesdll.lib b/bin/win64/libpqtypesdll.lib new file mode 100644 index 0000000..f0ce13c Binary files /dev/null and b/bin/win64/libpqtypesdll.lib differ diff --git a/source/AUTHORS b/source/AUTHORS new file mode 100644 index 0000000..7b0d309 --- /dev/null +++ b/source/AUTHORS @@ -0,0 +1,33 @@ + +LIBPQTYPES LIBRARY +------------------ + +Distributed by: + + eSilo, LLC. (see LICENSE file) + +Maintainers: +People who currently fix bugs and perform most of the forward development. + + Andrew Chernow achernow (at) gmail (dot) com + Merlin Moncure mmoncure (at) gmail (dot) com + +Written by: +Original people behind the libpqtypes project core functionality and +architecture (June 2007 - Apr 2008). The wounded warriors who battled the +PostgreSQL team trying to get libpqtypes into the main source tree. That +battle continues to be waged as we believe libpqtypes is a far more +advanced and powerful client interface for PostgreSQL than the vanilla libpq. + + Andrew Chernow achernow (at) gmail (dot) com + Merlin Moncure mmoncure (at) gmail (dot) com + +Contributors: +People who have proven to be helpful over the years with their critical +thinking, design ideas and bug detection. + + Jeremy Smith jeremy (at) esilo (dot) com + Dmitry Epstein depstein (at) alliedtesting (dot) com + +> Last updated 2015-02-27 + diff --git a/source/ChangeLog b/source/ChangeLog new file mode 100644 index 0000000..0465ef5 --- /dev/null +++ b/source/ChangeLog @@ -0,0 +1,364 @@ + +LIBPQTYPES CHANGE LOG +--------------------- + +Initially, libpqtypes was a libpq patch named (PQparam/PGparam) and can +be found on the -hackers and -patches mailing lists. This change log +begins at the point libpqtypes was put on pgfoundry as its own project. + +To gain access to the "(NOT RELEASED)" code, do a cvs checkout. + +(NOT RELEASED), v1.5.2 +- BUG: Fixed putting binary empty strings. Previously, the string was + incorrectly converted to SQL NULLs, but now are properly packed and + transmitted. + - Reported by: Dmitry Epstein +- FEATURE: Added ability to put SQL NULLs for int2, int4, int8, float4, float8, + numeric,"char", bool and money types. This is accomplished by using + the type-spec pointer flag "*" and allowing these types to be passed as + pointers: + >> + int *i = NULL; + if(something_is_true) + *i = 10; + PQputf(param, "%int4*", i); + >> + Before this change, the use of "*" flag for these types was ignored + and passing any of them as pointers was not allowed. + - Requested by: Dmitry Epstein and several on stackoverflow. + http://stackoverflow.com/questions/18684331/passing-sql-null-as-a-sql-statement-parameter-value-using-the-postgresql-libpqty +- Updated Copyright statement from 2011 to 2008 - 2015, affected all source + code and man page files. +- Update an example within docs/man3/PQputf.3 to include putting a NULL + using the pass-by-pointer type-specifier flag '*'. +- Updated docs/man3/pqt-specs.3 to include documentation in regard to putting + NULLs using the pass-by-pointer flag. +- Removed the use of pqt_allowsptr() in spec.c, All built-in types now support + the use of the pass-by-pointer '*' flag. This was only allowing + variable-length data types like text, pqt.str, bpchar, etc... + +Jun 27, 2013 v1.5.1 +- fixed issue in spec.c where the type name was not being lower cased + when it was required. +- Moved AUTHORS, ChangeLog, INSTALL, LICENSE and README to EXTRA_DIST + - Jozef Mlich +- Modified configure.ac and Makefile.am to properly do SO versioning. + - Jozef Mlich +- error.c uses the GCC contructor attribute so it can create the the + TLS key for each thread's error structure. For whatever reason, + calling the constructor function 'my_init' was causing it to never + get called?!? Renamed it and all is well. This becomes apparent + when libpqtypes is linked with apps using pthread_key_create. +- Removed -no-undefined from LDFLAGS in Makefile.am. This proved to be + annoying. +- Updated groff2html so it no longer includes an inline style for + pre tag width. +- Updated pqt-handlers.3, the PGtypeHandler structure was out of date. +- pqt-composites.3 man page had a bug when registering the simple and complex + composite. It was only indicating 1 type instead of 2. + +Apr 11, 2011 v1.5.0 +- Bumped version to 1.5.0 due to many API removals and additions. +- Added -avoid-version and -no-undefined to Makefile.am. +- Added PQinitTypes to replace PQtypesRegister. The new function + adds no new behavior, just a name change. +- Deprecated PQtypesRegister, use PQinitTypes in its place. +- PQclearTypes was added to allow one to clear all registered type handlers. +- Added an internal API pqt_cleartypes since clearing the type data + is now called from PQclearTypes and pqt_eventproc. +- Added a PQclearSpecs function that allows clearing all prepared specifiers + in a single call. Currently, specs can be cleared one at a time by + setting PQspecPrepare's 3rd 'format' argument to NULL. This is useful + but makes clearing all specs difficult because it is not always known + what specs have been prepared by all modules of an application. +- Removed "automatic" re-registering of types during PQresetXXX. This + fails miserably during asynchronous resets. Replaced with the + folowing call sequence: PQresetXXX, PQclearTypes, PQregisterTypes. + NOTE: It is not required to re-register type handlers after a PQresetXXX. + It is only useful if there is concern that registered types may have + gone stale. + WARNING: If an application is reliant upon automatic re-registration of + type handlers during a PQresetXXX, you will have to update your + code to manually do a PQclearTypes followed by PQregisterTypes just + after the reset call. +- Removed the orig_xxx members and regtype from PGtypeHandler structure. + These members were used by the reset event which was yanked. +- Finally 86'd PQregisterTypeHandler, which has been deprecated since + v1.4.0, around 18 months as of Apr 2011. +- Updated copyright date from 2009 to 2011, affected source code and + man pages. +- Updated AUTHORS file to include a list of maintainers and some verbage. +- Deprecated PQregisterSubClasses, PQregisterComposites and + PQregisterUserDefinedTypes in favor of PQregisterTypes. This new + function takes a 'which' argument that indicates if PQT_SUBCLASS, + PQT_COMPOSITE or PQT_USERDEFINED types are being registered. This + function provides full support for asynchronous type registration via + a simple 'async' boolean argument. The typical PQconsumeInput, PQisBusy + and PQgetResult should be used to obtain a result. After a result is + obtained, the new PQregisterResult function needs to be called + to complete the registration. +- Added PQregisterResult which registers the types found within + the result set. Added to support asynchronous type registration and + to avoid type lookups for every new connection by caching the PGresult + type data for use with this function. +- Add PQregisterTypes.3 man page and added it to the make system install. +- Add PQregisterResult.3 man page and added it to the make system install. +- Removed referneces in documentation and code examples to PQregisterSubClasses, + PQregisterComposites and PQregisterUserDefinedTypes. Replaced with + new PQregisterTypes call. +- Regression test now uses PQregisterTypes. NOTE: no regression test + for PQregisterResult or asynchronous type registration yet. +- Marked PQregisterSubClasses.3, PQregisterComposites.3 and + PQregisterUserDefinedTypes.3 man pages as deprecated. They are still + installed with a `make install` and will be until removed. +- Added PQparamDup, which given a PGparam will make an exact duplicate. + This is useful in cases where you want to queue qeuries to execute + at a later time, like a connection pooler. The problem is PGparamCreate + requires a connection object, which may not be available when attempting + to enqueue a query with its PGparam object. Instead, a PGparam object + can used for the sole purpose of creating duplicates while there are + no available PGconn objects. +- Updated PGparamCreate man page to include documentation about PGparamDup. + +Feb 26, 2010 v1.4.3 +- Fixed bug in handler.c. The type lookup query was matching typename + its array based on the array name being '_' plus the typename. This + was slower and unsafe. + +Oct 03, 2010 v1.4.2 +- Fixed bug in events.c. When clearing a type handler list the 'max' count + (amount allocated) was not set to zero. This caused a crash during + a PQreset call. +- Updated PQgetf documentation, clarified how to handle errors when + getting data types that require memory management: PGresult and PGarray. +- Moved libpq-events.h out of libpqtypes.h and into libpqtypes-int.h. + Added libpq-fe to libpqtypes.h. + +Apr 06, 2010 v1.4.1 +- Fix memory leak in events.c during PGEVT_CONNRESET. Not a big deal as the + leak occurs within an out-of-memory error case, so the process is most + likely toast. Either way, leak has been plugged. +- pqt_allowsptr was returning FALSE if the type's schema was not provided. + It now returns TRUE as it should. +- Fixed how missing types are handled during register functions. They were + not properly indicating errors in all cases. + +Nov 10, 2009 v1.4.0 +- Added PQgetvf to compliment PQgetf. PQgetf is now a light wrapper + PQgetvf. This did not change the behavior of PQgetf in any way. +- Fixed bug in IPv6 regression_test. When AF_INET6 is available, the + 'hints' parameter to getaddrinfo should be supplied with the ai_family + set to AF_INET6. +- 63% Performance improvement to the type handling duplication which occurs + whenever a result is copied or a PGparam is created. Previously, the + type handlers contained allocated strings for the identifier names: + like type and schema name. When duplicating the handlers, the strdup + cost added up to a noticeable amount of time (mostly seen during + PQgetf(a_composite_or_array) which duplicates the handlers when generating + a new PGresult). These strings were changed to fixed length buffers + 65 bytes wide; 64 max char identifier plus terminating NUL. The names + no longer need to be deep copied, they are instead copied as part of a + larger memcpy. Additionally, the attDescs array now uses a fixed length + buffer when the number of compsoite attrs is <= 16 (common case). When + larger, allocation is used. 'freeAttDescs' flag was added to the + PGtypeHandler structure to indicate if the attDescs requires freeing. + In total, these changes made type handling duplication 63% faster. + NOTE: + This increased the size of PGtypeHandler by around 434 bytes. The + size of PGrecordAttDesc increased by 65 bytes. This change modified + 2 public structures so please ensure your libpqtypes.h is current. +- Split PQregisterTypeHandler into three different functions: + PQregisterComposites, PQregisterUserDefinedTypes and PQregisterSubClasses. + This was done for two reasons: needed the ability to register multiple + types at the same time, avoid round trip issues, and simplify the interface. +- PQregisterTypeHandler is now deprecated and should no longer be used. It + exists only for compatibility reasons. It is a wrapper to + PQregisterComposites, PQregisterUserDefinedTypes and PQregisterSubClasses. +- The PGEVT_REGISTER event was not be handled properly by libpqtypes. It + neglected to initialize the internal type formatting information. +- Error case in pqt_parse() was accessing a NULL pointer. +- Bug in network.c when getting text inet/cidr types. Apparently, windows + requires a few hints about the address family when using getaddrinfo: + both IPv4 and IPv6. This must be a recent Microsoft winsock update as + this code used to work. + +Sep 15, 2009 v1.3.4 +- In v1.2b, a caching system for type spec format strings was introduced. + PQputf and PQgetf would cache the last spec format seen, like "%int4". + This avoided constant type handling lookups when using arrays, since + one must put/get every element. This version expands on this concept + by adding an additional API call: PQspecPrepare. The application can + now prepare an unlimited number of spec format strings. + NOTE: + This change does not effect the behavior of putf or getf in older versions. + Although, some libpqtypes code may run slower since type spec format + strings are no longer automatically cached. If you have loops calling + putf or getf with the same spec format string, typically arrays, you + would probably benefit from PQspecPrepare. +- Added a PQsendf() and PQsendvf(), same as PQexecf and PQexecvf except they + are asynchronous versions. Created manual pages for these functions as well. +- Fixed a bug in PQexecf and PQexevf documentation, the prototypes were + documented as returning an int rather than a PGresult*. +- Fixed spelling error in PQspecPrepare.3 man page and added PQsendf and + PQsendvf to the list of supported functions. +- Fixed memory leak in the error handling code. Multi-threaded windows + builds were not cleaning up the internal error structure in error.c. + This is because windows has no automatic cleanup for TLS storage. The + only solution was to use fixed length buffers and truncate unusually + long error messages; including error fields. + +May 29, 2009 v1.3.3 +- Added PQexecf and PQexecvf which allows one to pack parameters and + execute a command all in one statement. Example: + PQexecf(conn, "SELECT %int4 + %int4", 1, 1); +- Updated the PQputf.3 man page execf documentation which stated that + execf should be implemented by the application ... not libpqtypes. + Apparently, we no longer feel that way :) +- Fixed bug in datetime.c that accessed index 7 of a 7 byte array, ooppps. + Thanks to GCC version 4.3.2 for catching this one: + "src/datetime.c:930: warning: array subscript is above array bounds" + +Apr 27, 2009 v1.3.2 +- Slight change in the behavior of PQputf and PQputvf. These functions + will now silently auto-fill unset composite fields with NULLs. This only + pertains to the last N fields: if there are 10 fields in a compsoite + and only 7 are put, the last 3 fields will be auto-filled with SQL NULLs. + Previously, an error was raised indicating the client and server have + different ideas about the number of fields in the composite. That error + is still raised if the number of params exceeds the number of composite + fields. Auto-filling only occurs when the number of params falls short. +- User-defined CFLAGS were being lost due to a dumb assigment at the + top of the script. It now properly appends flags to the ones supplied + by the user. +- Updated PQgetf man page to include a warning about getting arrays and + composites. If getf fails after an array or composite was retrieved, + the PGresult representing the array/composite must still be cleared. + The solution is to get arrays/composites by themselves or to always + make them the last field in the getf call (the latter only works + if there is only one array or composite in the field list). +- Fixed SEGFAULT when getting a NULL compsoite array item. + +Feb 11, 2009 v1.3.1 +- PQgetf of an empty array was always returning a result with a single + tuple (array item), eventhough the PGarray members were correctly set + for an empty array. This has been fixed. +- Added empty array test to regression-test.c. +- getaddrinfo is missing on Windows 2000 and earlier versions. Wspiapi.h + and ws2tcpip.h must be included to provide a replacement. libpqtypes-int.h + neglected to include Wspiapi.h, which it now does. + Microsoft docs on issue: http://support.microsoft.com/kb/955045 + +Feb 03, 2009 v1.3.0 +- The internal put_int2 and put_int4 type handlers were passing a va_arg + call as an argument to the pqt_buf_putint2 and pqt_buf_putint4 macros. + This corrupted the value being put on UnixWare 7.1.4. The return + of va_arg is now stored in a local variable that is passed to + one of the putintX macros. +- Renamed PGinet sa_len member since it conflicts with some systems. + For instance: SCO UnixWare defines a sa_len macro in sys/socket.h + #define sa_len sa_un.sa_s.sa_saus_len + Renamed it to sa_buf_len and changed all references. +- network.c had an issue with struct sockaddr not being zero'd on AIX. + This caused all inet/cidr tests to fail. Update regresion-test.c + with similar fixes. +- AI_NUMERICHOST defined if not available in network.c. If getaddrinfo + fails with EAI_BADFLAGS, its tried again w/o numeric host. +- AIX defaults to an unsigned char, which broke "char" type handling. + Changed PGchar typedef to signed char and added the GCC option + -fsigned-char to configure.ac. +- Changed the return type of PQgeterror from a const char* to a char*. + Having it be const served no purpose. +- Added PQparamCount which returns the number of parameters in a PGparam. + Added PQparamCount.3 man page and updated Makefile. +- Made errFields implementation more memory efficient by allocating all + fields as a single reusable buffer. +- Reformatted code to match PostgreSQL Project. This changed every + source file so take note when looking at CVS diffs. +- Defined HAVE_VSNPRINTF in port.c when compiling for mingw or cygwin. +- Fixed man page errors in PQputf and pqt-handlers. +- Updated INSTALL file to include the 'install' and uninstall' targets. +- Makefile.am (unix make) now installs PQgetErrorField man page. +- Update copyright notices to reflect 2009: source files, LICENSE + and man pages. +- Removed typeargs state flag PUT_STATE_FREEOUT from param.c since it + is no longer used. + +Dec 03, 2008 v1.2e +- Added support for PQresultErrorField as PQgetErrorField. PQparamExec and + PQparamExecPrepared clear resutls in a failure status, thus removing + the abillity to get error fields. PQgetErrorField can now be used and + it doesn't require a result. + +Dec 02, 2008 v1.2d +- Fixed bug when putting a NULL array item; %null or %pqt.null. array.c + was not adding in the 4 byte length when the length's value was NULL_LEN. + This resulted in "ERROR: insufficient data left in message" errors from + the server during a PQparamExec() call. +- Added a %null test in regression-test.c for composites and arrays. + +Nov 18, 2008 v1.2c +- removed GCC __thread modifier from error.c. Only pthread TLS keys + are now used. The TLS key for the error system is now intialized + with the GCC function attribute "constructor"; which replaced _init(). + This GCC attribute has been around since GCC version 2.7.0, old enough. + The error system used to intialize the TLS key via pthread_once(), + but this function is broken on all version of Solaris prior to 10 + (its actually a stub function the returns 0). This means libpqtypes + versions prior to this one cannot use thread-safe mode on Solaris 9 or + below. Windows continues to use __declspec(thread). +- The PATH geo type was completely broken (SIGBUS) on many RISC processors, + we tested a couple of UltraSparcs, Itanium, PA-RISC and MIPS. This is + due to memory alignment issues (boy, spoiled by x86!). A configure + check was added to detect when strict memory alignment is required + (STRICT_MEMORY_ALIGNMENT) and the source is toggled appropriately. +- Add pqt_buf_xxx functions to abstract reading and writing data + to/from buffers. Another memory alignment change. +- fixed regression-test.c on hpux which was referencing unknown macros: + LLONG_MAX and LLONG_MIN. HPUX uses LONG_LONG_MAX. +- cleaned up a few things in Makefile.am (unix make file) +- win32.mak for MSVC had an issue with include variable. created INC2 + to solve this. + +Nov 11, 2008 v1.2b +- Added a type specifier caching system. Whenever putf or getf are + called, it compares the specifier string with the last one libpqtypes + saw. If it is a match, the parsing and type handler lookup + stage can be bipassed, giving a 25% performance increase for arrays + and large result sets. This also makes libpqtypes noticably faster + than using libpq in text mode ... PQexec. +- preprocessing bug in port.c, #elif HAVE_VSNPRINTF instead of + #elif defined(HAVE_VSNPRINTF). +- windows was completely broken from a change in v1.2a. Apparantley + windows has different memory addresses for DLL exported functions; + the address seen outside the DLL and the address seen from within the DLL. + This broke the PQtypesRegister macro that was referencing PQtypesEventProc, + since the external address was being used to register the event proc + but the internal address was being used by libpqtypes. This problem was + solved by converting PQtypesRegister to a function, which forces the + event proc to always be referenced from within libpqtypes. + PQtypesEventProc was completely removed form the public interface. +- Updated make system to be more configurable, allows setting things + like CC, CFLAGS, etc.. at the prompt (see INSTALL). + +Sept 26, 2008 v1.2a +- libpqtypes uses the libpq event system, an approaved patch for 8.4. + The object hooks patch was redesigned and than renamed to libpq events. +- bug fix in timestamp code +- memory leak fix in error system, didn't clean up thread memory. +- pqytpe's PGEventProc is now a public function named PQtypesEventProc + +May 19, 2008 v1.0c +- Bug fix in datetime.c. When performing a time value conversion, 1-based + and 0-based month values were being used/confused. +- libpqtypes using objecthooks, a proposed patch for libpq allowing + outside apps, like libpqtypes, to hook into libpq. + +May 09, 2008 v1.0b +- Added copyright notice to all source files. +- Updated several man pages. +- Added an include for stdarg.h to libpqtypes.h +- Updated makefile and groff2html + +April 28, 2008 v1.0 +- libpqtypes project was created on pgfoundry +- make system was put into place (mingw, cygwin and msvc support) diff --git a/source/INSTALL b/source/INSTALL new file mode 100644 index 0000000..5a80559 --- /dev/null +++ b/source/INSTALL @@ -0,0 +1,119 @@ + +LIBPQTYPES INSTALLTION GUIDE +---------------------------- + +*) UNIX BUILDS + +For quick builds, run the below: + + ]# ./configure + ]# make + ]# make install + + (Also `make uninstall', which removes all installed headers, + libs and mans pages) + +By default, only a shared library is built. To build a static +library, use the `--enable-static' option. + +To build a thread-safe version, use the `--enable-thread-safety' option. +To use libpqtypes in a thread-safe environment, the system must have +pthreads installed. Most systems will have /usr/include/pthread.h +and /usr/lib/libpthread.so or /usr/lib/libthread.so. + +The default prefix is `/usr/local', so if the `--prefix' option is +not specified the header file(s) will be installed at +/usr/local/include and the library file(s) will be installed at +/usr/local/lib. + +If your system doesn't include a `long long' data type, resulting in +a library compile error, you can specify an alternative by setting +PQT_LONG_LONG=data_type during configure. + + ]# CFLAGS="-DPQT_LONG_LONG=my_int8" ./configure + +Files installed: + + - $(prefix)/include/libpqtypes.h + - $(prefix)/lib/libpqtypes.so + +When `--enable-static' is specified: + + - $(prefix)/lib/libpqtypes.a + +To dynamically link with libpqtypes, pass -lpqtypes to the linker. + + + +*) MINGW AND CYGWIN BUILDS + +There is no configure for MinGW or Cygwin. Execute the below from +the root of the source tar ball within the cygwin shell or msys. + + ]# make -f Makefile.win32 [options] [targets] + + + +*) MSVC BUILDS + +MSVC versions 6, 7 and 8 have all been tested. Versions prior to 6 +may work but are not supported. + +The MSVC build uses `nmake' and can be executed from the root of the +source tar ball at a DOS prompt: + + > nmake -f win32.mak [options] [targets] + + + +*) MINGW, CYGWIN & MSVC BUILDS: + +For the below examples, MAKE is defined as: + + # MSVC + MAKE = nmake -f win32.mak + + # CYGWIN & MINGW + MAKE = make -f Makefile.win32 + +Targets: + + all - build the libpqtypes library + test - build the libpqtypes regression test + clean - delete all files generated by compiles + install - Install the binaries, headers and man3 pages + uninstall - Uninstalls libpqtypes, reverse of install target + +Thread-safe: + + $(MAKE) MT=1 + + NOTE: MinGW and/or Cygwin may require installing pthreads. + +Specify alternative 64-bit int data type: + + $(MAKE) PQT_LONG_LONG=my_int8 + +Global make & nmake compiler/linker variables: + + CC = C compiler + INC = includes + CFLAGS = compiler flags + LPATH = library path. MINGW and CYGWIN use -L while MSVC can supply a single path + + # If libpq is not installed and you use MSVC, try this (replace paths): + > $(MAKE) INC="-Ic:\pgsql\src\interfaces\libpq" LPATH="-Ic:\pgsql\src\interfaces\libpq\Release" + +Files generated: + + libpqtypes.dll - Windows DLL + libpqtypesdll.lib - Import lib for DLL + libpqtypes.lib - Static library + +Where to install files: + + - System Include Path: libpqtypes.h + - System Library Path: libpqtypes.dll + - System Library Path: libpqtypesdll.lib + - System Library Path: libpqtypes.lib (static lib) + diff --git a/source/LICENSE b/source/LICENSE new file mode 100644 index 0000000..9bd43f4 --- /dev/null +++ b/source/LICENSE @@ -0,0 +1,22 @@ + +LIBPQTYPES +Software extension to the PostgreSQL libpq interface. + +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement +is hereby granted, provided that the above copyright notice and this +paragraph and the following two paragraphs appear in all copies. + +IN NO EVENT SHALL ESILO, LLC. BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF +ESILO, LLC. HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +ESILO, LLC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND ESILO, LLC. HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +ENHANCEMENTS, OR MODIFICATIONS. + diff --git a/source/Makefile.am b/source/Makefile.am new file mode 100644 index 0000000..182e6e1 --- /dev/null +++ b/source/Makefile.am @@ -0,0 +1,68 @@ +AM_LDFLAGS = +AUTOMAKE_OPTIONS = foreign +@SET_MAKE@ + +srcfiles = src/array.c src/datetime.c src/error.c \ + src/events.c src/exec.c src/geo.c src/handler.c \ + src/misc.c src/network.c src/numerics.c src/param.c \ + src/port.c src/record.c src/spec.c src/utils.c src/varlena.c \ + src/libpqtypes.h src/libpqtypes-int.h + +manpages = docs/man3/PQgeterror.3 docs/man3/PQgetf.3 \ + docs/man3/PQlocalTZInfo.3 docs/man3/PQparamClear.3 \ + docs/man3/PQparamCreate.3 docs/man3/PQparamExec.3 \ + docs/man3/PQparamExecPrepared.3 docs/man3/PQparamReset.3 \ + docs/man3/PQparamSendQuery.3 docs/man3/PQparamSendQueryPrepared.3 \ + docs/man3/PQputf.3 docs/man3/PQputvf.3 \ + docs/man3/PQseterror.3 docs/man3/pqt-composites.3 docs/man3/pqt-handlers.3 \ + docs/man3/PQgetErrorField.3 docs/man3/pqt-specs.3 \ + docs/man3/PQtypesRegister.3 docs/man3/PQparamCount.3 \ + docs/man3/PQexecf.3 docs/man3/PQexecvf.3 docs/man3/PQspecPrepare.3 \ + docs/man3/PQsendf.3 docs/man3/PQsendvf.3 docs/man3/PQgetvf.3 \ + docs/man3/PQregisterComposites.3 docs/man3/PQregisterUserDefinedTypes.3 \ + docs/man3/PQregisterSubClasses.3 docs/man3/PQregisterTypes.3 \ + docs/man3/PQregisterResult.3 docs/man3/PQinitTypes.3 \ + docs/man3/PQclearTypes.3 docs/man3/PQclearSpecs.3 + +lib_LTLIBRARIES = libpqtypes.la +libpqtypes_la_SOURCES = $(srcfiles) +libpqtypes_la_LDFLAGS = -version-info $(LIBPQTYPES_SO_VERSION) +include_HEADERS = src/libpqtypes.h +dist_man_MANS = $(manpages) +noinst = src/libpqtypes-int.h src/getaddrinfo.h + +EXTRA_DIST = install-sh src/regression-test.c src/getaddrinfo.h \ + win32.mak groff2html Makefile.win32 \ + AUTHORS ChangeLog INSTALL LICENSE README + +.PHONY: test +test: + $(CC) $(CFLAGS) $(CPPFLAGS) -DHAVE_CONFIG_H \ + -o test src/regression-test.c $(LDFLAGS) $(LIBS) -lpq -lpqtypes + @rm -f regression-test.o + +.PHONY: docs +docs: + @find docs/man3 -name '*.3' -type f | xargs -i ./groff2html \{\} + +uninstall: + rm -f ${prefix}/include/libpqtypes.h + rm -f ${prefix}/lib/libpqtypes.* + @if [ 1 ] ; then \ + _manpath=`man -w PQgetf`; \ + if test ! -z $$_manpath ; then \ + manpath=`dirname $$_manpath`; \ + for man in $(manpages); do \ + man=`basename $$man`; \ + if test -f $${manpath}/$$man ; then \ + echo rm -f $${manpath}/$$man; \ + rm -f $${manpath}/$$man; \ + fi; \ + done \ + fi; \ + fi; + + + + + diff --git a/source/Makefile.win32 b/source/Makefile.win32 new file mode 100644 index 0000000..d9a2077 --- /dev/null +++ b/source/Makefile.win32 @@ -0,0 +1,49 @@ +############################################################## +# Project: libpqtypes +# Makefile for Cygwin or Mingw Environments (GCC) +# +# make -f Makefile.win32 [options] [targets] +# +# For further build instructions, see the package's INSTALL file. +# +# Authors: Andrew Chernow, Merlin Moncure +# Contact: libpqtypes@esilo.com +############################################################## + +PROJNAME = libpqtypes + +DEPS = src/libpqtypes-int.h src/libpqtypes.h +OBJECTS = src/array.o src/datetime.o src/error.o \ + src/events.o src/exec.o src/geo.o src/handler.o \ + src/misc.o src/network.o src/numerics.o \ + src/param.o src/port.o src/record.o src/spec.c \ + src/utils.o src/varlena.o + +INC += -Isrc +LIBS = -lpq -lws2_32 +CFLAGS += -s -Wall -Wpointer-arith -D_GNU_SOURCE -O3 \ + -std=gnu99 -Wlong-long -D_WIN32_WINNT=0x0501 -D_REENTRANT + +ifdef MT + CFLAGS += -DPQT_THREAD_SAFE -D_THREAD_SAFE + LIBS += -lpthread +endif + +ifdef PQT_LONG_LONG + CFLAGS += -DPQT_LONG_LONG=$(PQT_LONG_LONG) +endif + +all: $(OBJECTS) + dllwrap -o $(PROJNAME).dll -dllname $(PROJNAME).dll $(OBJECTS) $(LPATH) $(LIBS) + dlltool --dllname $(PROJNAME).dll --output-lib $(PROJNAME).a + +test: + gcc $(CFLAGS) $(INC) -o regtest src/regression-test.c $(PROJNAME).dll $(LPATH) $(LIBS) + -@rm -f regression-test.o + +%.o: %.c $(DEPS) + gcc $(CFLAGS) $(INC) -o $@ -c $< + +clean: + -@rm -f $(OBJECTS) $(PROJNAME).so $(PROJNAME).a regtest + diff --git a/source/README b/source/README new file mode 100644 index 0000000..bdd6b97 --- /dev/null +++ b/source/README @@ -0,0 +1,54 @@ + +LIBPQTYPES LIBRARY +------------------ + +This package contains the source code, documentation and management +files for the libpqtypes library, an extension to the PostgreSQL +libpq library. + +libpqtypes is a libpq extension that offers a new way of handling +parameterized queries and getting result field values. Both +putting parameters and getting values use a printf/scanf style +interface, with consistent specifiers for both. + + - Full support for binary and text format (parameter and result) + + - Full support for composites, arrays and composite arrays. + + - printf style interface to libpq's binary parameterized API. + + - scanf style interface for getting values: PQgetvalue extension. + + - Ability to register user-defined types, aliases and data type + sub-classes for use with printf style interfaces: ex. "%mytype". + + - Per-thread global error message: PQgeterror, PQseterror. + + - Online docs as well as man pages. + +INSTALL +libpqtypes cannot be used by itself, it can only be used with libpq. +To build and install libpqtypes, see the INSTALL file included with +this package. + +LICENSE +libpqtypes is released under the BSD license just like all software +components of the PostgreSQL Database Management System. See the +LICENSE file included with this package. + +AUTHORS +libpqtypes is a contribution of eSilo, LLC and was written by +Andrew Chernow and Merlin Moncure. See the AUTHORS file. + +To report bugs, feature requests or general questions goto the +libpqtypes forum at http://pgfoundry.org/forum/?group_id=1000370 +or send an email to . + +Online documentation: + + http://libpqtypes.esilo.com/ + +The latest version: + + http://pgfoundry.org/projects/libpqtypes/ + diff --git a/source/acinclude.m4 b/source/acinclude.m4 new file mode 100644 index 0000000..ad3001c --- /dev/null +++ b/source/acinclude.m4 @@ -0,0 +1,139 @@ +AC_DEFUN([AX_CFLAGS_GCC_OPTION_NEW], [dnl +AS_VAR_PUSHDEF([FLAGS],[CFLAGS])dnl +AS_VAR_PUSHDEF([VAR],[ac_cv_cflags_gcc_option_$1])dnl +AC_CACHE_CHECK([m4_ifval($2,$2,FLAGS) for gcc m4_ifval($1,$1,-option)], +VAR,[VAR="no, unknown" + AC_LANG_SAVE + AC_LANG_C + ac_save_[]FLAGS="$[]FLAGS" +for ac_arg dnl +in "-pedantic -Werror % m4_ifval($1,$1,-option)" dnl GCC + "-pedantic % m4_ifval($1,$1,-option) %% no, obsolete" dnl new GCC + # +do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'` + AC_TRY_COMPILE([],[return 0;], + [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break]) +done + FLAGS="$ac_save_[]FLAGS" + AC_LANG_RESTORE +]) +case ".$VAR" in + .ok|.ok,*) m4_ifvaln($3,$3) ;; + .|.no|.no,*) m4_ifvaln($4,$4) ;; + *) m4_ifvaln($3,$3,[ + if echo " $[]m4_ifval($2,$2,FLAGS) " | grep " $VAR " 2>&1 >/dev/null + then AC_RUN_LOG([: m4_ifval($2,$2,FLAGS) does contain $VAR]) + else AC_RUN_LOG([: m4_ifval($2,$2,FLAGS)="$m4_ifval($2,$2,FLAGS) $VAR"]) + m4_ifval($2,$2,FLAGS)="$m4_ifval($2,$2,FLAGS) $VAR" + fi ]) ;; +esac +AS_VAR_POPDEF([VAR])dnl +AS_VAR_POPDEF([FLAGS])dnl +]) + + +dnl the only difference - the LANG selection... and the default FLAGS + +AC_DEFUN([AX_CXXFLAGS_GCC_OPTION_NEW], [dnl +AS_VAR_PUSHDEF([FLAGS],[CXXFLAGS])dnl +AS_VAR_PUSHDEF([VAR],[ac_cv_cxxflags_gcc_option_$1])dnl +AC_CACHE_CHECK([m4_ifval($2,$2,FLAGS) for gcc m4_ifval($1,$1,-option)], +VAR,[VAR="no, unknown" + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_[]FLAGS="$[]FLAGS" +for ac_arg dnl +in "-pedantic -Werror % m4_ifval($1,$1,-option)" dnl GCC + "-pedantic % m4_ifval($1,$1,-option) %% no, obsolete" dnl new GCC + # +do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'` + AC_TRY_COMPILE([],[return 0;], + [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break]) +done + FLAGS="$ac_save_[]FLAGS" + AC_LANG_RESTORE +]) +case ".$VAR" in + .ok|.ok,*) m4_ifvaln($3,$3) ;; + .|.no|.no,*) m4_ifvaln($4,$4) ;; + *) m4_ifvaln($3,$3,[ + if echo " $[]m4_ifval($2,$2,FLAGS) " | grep " $VAR " 2>&1 >/dev/null + then AC_RUN_LOG([: m4_ifval($2,$2,FLAGS) does contain $VAR]) + else AC_RUN_LOG([: m4_ifval($2,$2,FLAGS)="$m4_ifval($2,$2,FLAGS) $VAR"]) + m4_ifval($2,$2,FLAGS)="$m4_ifval($2,$2,FLAGS) $VAR" + fi ]) ;; +esac +AS_VAR_POPDEF([VAR])dnl +AS_VAR_POPDEF([FLAGS])dnl +]) + +AC_DEFUN([AX_CFLAGS_GCC_OPTION],[ifelse(m4_bregexp([$2],[-]),-1, +[AX_CFLAGS_GCC_OPTION_NEW($@)],[AX_CFLAGS_GCC_OPTION_OLD($@)])]) + +AC_DEFUN([AX_CXXFLAGS_GCC_OPTION],[ifelse(m4_bregexp([$2],[-]),-1, +[AX_CXXFLAGS_GCC_OPTION_NEW($@)],[AX_CXXFLAGS_GCC_OPTION_OLD($@)])]) + +AC_DEFUN([AC_C_LONG_LONG], +[AC_CACHE_CHECK(for long long int, ac_cv_c_long_long, +[if test "$GCC" = yes; then + ac_cv_c_long_long=yes + else + AC_TRY_COMPILE(,[long long int i;], + ac_cv_c_long_long=yes, + ac_cv_c_long_long=no) + fi]) + if test $ac_cv_c_long_long = yes; then + AC_DEFINE(HAVE_LONG_LONG, 1, [compiler understands long long]) + fi +]) + +dnl AC_CHECK_FUNC_IN(HEADER, FUNCTION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +AC_DEFUN([AC_CHECK_FUNC_IN], +[AC_MSG_CHECKING([for $2 in $1]) +AC_CACHE_VAL(ac_cv_func_$2, +[AC_TRY_LINK( +dnl Don't include because on OSF/1 3.0 it includes +dnl which includes which contains a prototype for +dnl select. Similarly for bzero. +[/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2(); below. */ +#include +#include <$1> +/* Override any gcc2 internal prototype to avoid an error. */ +]ifelse(AC_LANG, CPLUSPLUS, [#ifdef __cplusplus +extern "C" +#endif +])dnl +[/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $2(); +], [ +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$2) || defined (__stub___$2) +choke me +#else +$2(); +#endif +], eval "ac_cv_func_$2=yes", eval "ac_cv_func_$2=no")]) +if eval "test \"`echo '$ac_cv_func_'$2`\" = yes"; then + AC_MSG_RESULT(yes) + ifelse([$3], , :, [$3]) +else + AC_MSG_RESULT(no) +ifelse([$4], , , [$4 +])dnl +fi +]) + +dnl AC_CHECK_FUNCS_IN(HEADER, FUNCTION... [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +AC_DEFUN([AC_CHECK_FUNCS_IN], +[for ac_func in $2 +do +AC_CHECK_FUNC_IN($1, $ac_func, + ac_tr_func=HAVE_`echo $ac_func | sed -e 'y:abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:' -e 's:[[^A-Z0-9]]:_:g'` + AC_DEFINE_UNQUOTED($ac_tr_func) $3], $4)dnl +done +]) + diff --git a/source/configure.ac b/source/configure.ac new file mode 100644 index 0000000..0d9a95c --- /dev/null +++ b/source/configure.ac @@ -0,0 +1,157 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.57) +AC_INIT(libpqtypes, 1.5.2a, libpqtypes@esilo.com) +AC_CANONICAL_TARGET +AC_PROG_MAKE_SET +SET_MAKE="MAKE=make --no-print-directory" +AC_CONFIG_AUX_DIR([.]) +AM_INIT_AUTOMAKE(libpqtypes,1.5.1) +AC_SUBST([LIBPQTYPES_SO_VERSION], [1:5:1]) +AC_DISABLE_STATIC +AC_PROG_LIBTOOL +AC_PROG_INSTALL +AC_CONFIG_SRCDIR([src/param.c]) +AM_CONFIG_HEADER([src/pqt_config.h:src/pqt_config.h.in]) +AC_PROG_CXX +AC_PROG_CC + +CPPFLAGS="$CPPFLAGS -Isrc" +if test "$CC" = "gcc" ; then + # always want reentrant funcs, not just thread-safe mode. -s + CFLAGS="$CFLAGS -O2 -Wall -Wpointer-arith -D_GNU_SOURCE -D_REENTRANT -fsigned-char" + + # gcc doesn't indicate an error for unknown options when they are + # not warning/feature options, -Wxxx or -fxxx. We have to test manually. + cprog="int main(void){return 0;}" + gcctest="gcc -x c -c - -o /dev/null" + + AC_MSG_CHECKING(CFLAGS for gcc -std=gnu99...) + result=`echo "$cprog" | $gcctest -std=gnu99 2>&1` + if test -z "$result" ; then + CFLAGS="$CFLAGS -std=gnu99" + AC_MSG_RESULT(-std=gnu99) + else + AC_MSG_RESULT([no, unknown]) + fi + + # turn on as many warnings as possible + AX_CFLAGS_GCC_OPTION([-Wclobbered]) + AX_CFLAGS_GCC_OPTION([-Wempty-body]) + AX_CFLAGS_GCC_OPTION([-Wignored-qualifiers]) + #AX_CFLAGS_GCC_OPTION([-Wmissing-field-initializers]) # annoying + AX_CFLAGS_GCC_OPTION([-Wmissing-parameter-type]) + AX_CFLAGS_GCC_OPTION([-Wold-style-declaration]) + AX_CFLAGS_GCC_OPTION([-Woverride-init]) + AX_CFLAGS_GCC_OPTION([-Wsign-compare]) + AX_CFLAGS_GCC_OPTION([-Wtype-limits]) + AX_CFLAGS_GCC_OPTION([-Wuninitialized]) + AX_CFLAGS_GCC_OPTION([-fomit-frame-pointer]) + AX_CFLAGS_GCC_OPTION([-fno-strict-aliasing]) + AX_CFLAGS_GCC_OPTION([-funroll-all-loops]) + AX_CFLAGS_GCC_OPTION([-funit-at-a-time]) +else + AC_MSG_ERROR([Using $CC instead of gcc, currently not supported]) +fi + +# Each case has an example of what target_os contains +case ${target_os} in + # solaris + solaris*) + CFLAGS="$CFLAGS -D_STDC_C99" + ;; + + # SCO UnixWare + *UnixWare*|*unixware*) + CFLAGS="$CFLAGS -D__UnixWare__" + ;; + + # SCO OpenServer + sco*) + CFLAGS="$CFLAGS -D__OpenServer__" + ;; +esac + +AC_ARG_ENABLE(thread-safety, [ --enable-thread-safety Enable threads], + [have_threads=yes], [have_threads=no]) + +if test "$have_threads" = "yes" ; then + AC_SEARCH_LIBS(pthread_create, [pthread thread],, + [AC_MSG_ERROR([Missing libpthread.so or libthread.so, cannot use --enable-thread-safety])]) + CFLAGS="$CFLAGS -DPQT_THREAD_SAFE -D_THREAD_SAFE" + + # SCO OpenServer 5, possibly other platforms, requires using FSU pthreads. + # We detect this by looking at the prototype for pthread_getspecific. + # There are a few other prototypes that are different as well but detecting + # one is plenty. + AC_MSG_CHECKING(whether FSU pthreads is being used) + AC_TRY_COMPILE( + [#include ], + [pthread_getspecific((pthread_key_t)(0), (void *)(0))], + [AC_DEFINE(PTHREAD_FSU, 1, + [Define to 1 if using FSU pthreads]) AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no)] + ) +fi + +# If strict memory alignment is required, this test will produce a +# Bus error. Most riscs CPUs (old and modern) are strict. +save_CFLAGS=$CFLAGS +CFLAGS="" +AC_MSG_CHECKING(if strict memory alignment is required) +AC_RUN_IFELSE([int main(void){char b[[8]];int*i=(int*)(b+1);*i=0;return 0;}], +[AC_MSG_RESULT(no)], [AC_DEFINE(STRICT_MEMORY_ALIGNMENT, 1, + [Define to 1 if architecture requires strict memory alignment]) AC_MSG_RESULT(yes)] +) +CFLAGS=$save_CFLAGS + +AC_HEADER_STDC +AC_CHECK_HEADERS([ \ +arpa/inet.h limits.h linux/limits.h netdb.h netinet/in.h stddef.h \ +sys/socket.h time.h math.h sys/time.h strings.h]) + +AC_STRUCT_TIMEZONE +AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,, \ +[#ifdef HAVE_TIME_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif]) + +AC_CHECK_TYPES(socklen_t,,, +[#include +#ifdef HAVE_APRA_INET_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif]) + +AC_CHECK_MEMBERS([struct sockaddr_storage.ss_len],,, \ +[#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif]) + +AC_C_CONST +AC_FUNC_MEMCMP +AC_FUNC_STRTOD +AC_HEADER_TIME +AC_TYPE_SIZE_T + +AC_SEARCH_LIBS(pow, m) +AC_SEARCH_LIBS(getaddrinfo, [nsl socket], \ + [AC_DEFINE([HAVE_GETADDRINFO], [], \ + [Define if getaddrinfo exists])], [], []) + +AC_CHECK_FUNCS([vsnprintf floor ceil rint hstrerror \ +localtime_r strtol strtoll strtoul]) + +AC_OUTPUT(Makefile) + + + diff --git a/source/docs/man3/PQclearSpecs.3 b/source/docs/man3/PQclearSpecs.3 new file mode 100644 index 0000000..85fad56 --- /dev/null +++ b/source/docs/man3/PQclearSpecs.3 @@ -0,0 +1 @@ +.so man3/PQspecPrepare.3 \ No newline at end of file diff --git a/source/docs/man3/PQclearTypes.3 b/source/docs/man3/PQclearTypes.3 new file mode 100644 index 0000000..ef0a6e4 --- /dev/null +++ b/source/docs/man3/PQclearTypes.3 @@ -0,0 +1 @@ +.so man3/PQinitTypes.3 diff --git a/source/docs/man3/PQexecf.3 b/source/docs/man3/PQexecf.3 new file mode 100644 index 0000000..3b0e800 --- /dev/null +++ b/source/docs/man3/PQexecf.3 @@ -0,0 +1,104 @@ +.TH "PQexecf" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQexecf \- Prepares parameters and executes a command. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +PGresult *PQexecf(const PGconn *\fIconn\fP, const char *\fIcmd\fP, ...); +.br +PGresult *PQexecvf(const PGconn *\fIconn\fP, const char *\fIcmd\fP, va_list \fIap\fP); +.br +int PQsendf(const PGconn *\fIconn\fP, const char *\fIcmd\fP, ...); +.br +int PQsendvf(const PGconn *\fIconn\fP, const char *\fIcmd\fP, va_list \fIap\fP); +\fP +.SH DESCRIPTION +.LP +The \fIPQexecf\fP() function executes a command that uses libpqtypes +type specifiers instead of $1, $2, etc... syntax. The idea +is to combine \fIPQputvf\fP() and \fIPQparamExec\fP() into a single call. +The variable argument list must match the type specs listed within the +cmd. The type specifiers should be placed where one would normally place +$1, $2, etc... + +The \fIPQexecvf\fP() function is identical to \fIPQexecf\fP() except +it takes a va_list. + +The \fIPQsendf\fP() and \fIPQsendvf\fP() functions are identical to +\fIPQexecf\fP() and \fIPQexecvf\fP() except they are asynchronous versions, +meaning an additional call, PQgetResult, must be issued to get the PGresult +object. For more information, see the PostgreSQL Documentation: specifically +the "Asynchronous Command Processing" section under +"Client Interfaces | libpq - C library". + +A prepared type spec "@name" can be used with these functions, granted that +\fIPQspecPrepare\fP() was called with a non-zero value for the is_stmt +argument. These functions needs an actual SQL statement to execute. +.SH RETURN VALUE +.LP +PQexecf and PQexecvf return NULL on error and a valid PGresult on success. +PQsendf and PQsendvf return zero on error and a non-zero value on success. +On error, use \fIPQgeterror(3)\fP to obtain an error message. +.SH EXAMPLES +.LP +.SS Using PQexecf +The example uses PQexecf function to execute a query. +.RS +.nf +.LP +\fB +/* The PQexecf call is shorthand for: + * + * PGparam *param = PQparamCreate(conn); + * PQputf(param, "%int4 %int4", 1, 1); + * res = PQparamExec(conn, param, "SELECT $1 + $2", 1); + * PQparamClear(param); + * + * As you may notice, PQexecf makes life much simpler. + */ +PGresult *res = PQexecf(conn, "SELECT %int4 + %int4", 1, 1); + +if(!res) + fprintf(stderr, "PQexecf failed: %s", PQgeterror()); +else + PQclear(res); + +/* A bit more common, this puts an int4 and a text into a generated + * PGparam and then executes 'myfunc($1, $2)' + */ +res = PQexecf(conn, "SELECT * FROM myfunc(%int4, %text)", 2, "abc"); + +/* Prepared type spec example. To use with execf, the final "is_stmt" + * argument must be set to a non-zero value! + */ +PQspecPrepare(conn, "account_insert", "INSERT INTO account VALUES " + "(%int8, %text, %int4)", 1); + +PGint8 acc_id = 78236; +PGtext acc_name = "ABC Coders, LLC."; +PGint4 acc_biz = ACC_BIZ_TECHNOLOGY; + +PQexecf(conn, "@account_insert", acc_id, acc_name, acc_biz); +\fP +.fi +.RE +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIPQgeterror(3)\fP, \fIPQputvf(3)\fP, \fIPQparamExec(3)\fP + diff --git a/source/docs/man3/PQexecvf.3 b/source/docs/man3/PQexecvf.3 new file mode 100644 index 0000000..77c4e1e --- /dev/null +++ b/source/docs/man3/PQexecvf.3 @@ -0,0 +1 @@ +.so man3/PQexecf.3 \ No newline at end of file diff --git a/source/docs/man3/PQgetErrorField.3 b/source/docs/man3/PQgetErrorField.3 new file mode 100644 index 0000000..e1ca3a1 --- /dev/null +++ b/source/docs/man3/PQgetErrorField.3 @@ -0,0 +1 @@ +.so man3/PQgeterror.3 \ No newline at end of file diff --git a/source/docs/man3/PQgeterror.3 b/source/docs/man3/PQgeterror.3 new file mode 100644 index 0000000..4c9fead --- /dev/null +++ b/source/docs/man3/PQgeterror.3 @@ -0,0 +1,56 @@ +.TH "PQgeterror" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQgeterror, PQseterror, PQgetErrorField \- libpqtypes error system management functions +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +char *PQgeterror(void); +.br +void PQseterror(const char *\fIformat\fP, ...); +.br +char *PQgetErrorField(int \fIfieldcode\fP); +\fP +.SH DESCRIPTION +.LP +The libpqtypes library maintains a per-thread global error message. The error +message is managed by \fIPQgeterror\fP() and \fiPQseterror\fP(). + +\fiPQseterror\fP() takes a regular printf compatible \fIformat\fP string for +setting the libpqtypes error message. + +\fiPQgetErrorField\fP() gets an error field from the last executed query. +The error field is only set by PQparamExec and PQparamExecPrepared. When +using libpq functions that return results, like PQexec, continue to use +PQresultErrorField. + +To clear the libpqtypes error message, call \fiPQseterror\fP() with a NULL +\fIformat\fP string: PQseterror(NULL); This will also clear error fields. +.SH RETURN VALUE +.LP +\fIPQgeterror\fP() returns the last error message for the calling thread. It will +never return NULL. +.LP +\fIPQgetErrorField\fP() returns the error field, referenced by fieldcode, for +the last executed query. NULL is returned if no value exists. +.SH EXAMPLES +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIpqt-specs(3)\fP diff --git a/source/docs/man3/PQgetf.3 b/source/docs/man3/PQgetf.3 new file mode 100644 index 0000000..d1d8a98 --- /dev/null +++ b/source/docs/man3/PQgetf.3 @@ -0,0 +1,104 @@ +.TH "PQgetf" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQgetf \- Gets one or more values from a PGresult in a scanf fashion. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +int PQgetf(const PGresult *\fIres\fP, int \fItup_num\fP, +.br + const char *\fIformat\fP, ...); +.br +int PQgetvf(const PGresult *\fIres\fP, int \fItup_num\fP, +.br + const char *\fIformat\fP, va_list ap); +\fP +.SH DESCRIPTION +.LP +The \fIPQgetf\fP() and \fIPQgetvf\fP() functions get one or more field +values from a PGresult using a scanf style interface. The \fItup_num\fP +argument indicates which tuple to read values from; values can +only be read from one tuple at a time. + +The \fIformat\fP argument is a data type specifier string +indicating the values to retrieve, like %int4 or #timestamp. Any +number of fields, in any order, can be retrieved in a single call. +Each data type specifier has a cooresponding \fBfield_num, type-args, [...]\fP +contained within the function\'s variable argument list. The +\fBfield_num\fP either indicates tuple field number or tuple field +name, depending on whether the data type in \fIformat\fP used a \'%\' +or \'#\' specifer mark (\`man \fIpqt-specs(3)\fP\'). The \fBtype-args\fP can be one or more +arguments required by the specific data type: for example, "%int4" +would require a pointer to a PGint4. All built-in PostgreSQL data +types require a single pointer value. +.SH RETURN VALUE +.LP +On success, a non-zero value is returned. On error, zero is +returned and \fIPQgeterror(3)\fP will contain an error message. + +If the retrieval of any field fails, the get operation is aborted and +function will fail. Eventhough some fields may have succeeded prior to +the failure, their values should not be trusted. Instead, make +another \fIPQgetf\fP() call. Getting an array or a composite can lead +to memory leaks when getf fails. This is because these types allocate +a PGresult object that must be cleared. To avoid leaks, it is recommended +to perform cleanup even if getf fails, retrieve arrays and composites by +themselves or make them the last field in a getf call. +.SH EXAMPLES +.LP +.SS Using PQgetf on a PGresult +The example uses the libpq PQexec function to execute a query and then uses +\fIPQgetf\fP() to retrieve field values. It is important to note that +libpqtypes execution functions, like \fIPQparamExec(3)\fP, do not need to +generate the PGresult provided to \fIPQgetf\fP(). +.RS +.nf +.LP +\fBint success; +PGint4 i4; +PGtext text; +PGbytea bytea; +PGpoint pt; +PGresult *res = PQexec(conn, "SELECT i,t,b,p FROM tbl"); + +/* Get some field values from the result (order doesn\'t matter) */ +success = PQgetf(res, + 0, /* get field values from tuple 0 */ + "%int4 #text %bytea %point", + /* type format specifiers (get text by name \'#\') */ + 0, &i4, /* get an int4 from field num 0 */ + "t", &text, /* get a text from field name "t" */ + 2, &bytea, /* get a bytea from field num 2 */ + 3, &pt); /* get a point from field num 3 */ + +/* Output an error message using PQgeterror(3). */ +if(!success) + fprintf(stderr, "*ERROR: %s\\n", PQgeterror()); + +/* Output the values, do this before PQclear() */ +else + printf("int4=%d, text=%s, bytea=%d bytes, point=(%f,%f)\\n", + i4, text, bytea.len, pt.x, pt.y); + +PQclear(res); +\fP +.fi +.RE +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIpqt-specs(3)\fP, \fIPQgeterror(3)\fP diff --git a/source/docs/man3/PQgetvf.3 b/source/docs/man3/PQgetvf.3 new file mode 100644 index 0000000..4fd6848 --- /dev/null +++ b/source/docs/man3/PQgetvf.3 @@ -0,0 +1 @@ +.so man3/PQgetf.3 diff --git a/source/docs/man3/PQinitTypes.3 b/source/docs/man3/PQinitTypes.3 new file mode 100644 index 0000000..4d592ac --- /dev/null +++ b/source/docs/man3/PQinitTypes.3 @@ -0,0 +1,56 @@ +.TH PQinitTypes 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQinitTypes \- Initializes libpqtypes with the libpq event system. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +int PQinitTypes(PGconn *conn); +.br +int PQclearTypes(PGconn *\fIconn\fP); +\fP +.SH DESCRIPTION +.LP +libpqtypes makes use of the libpq Event System. Before using +libpqtypes, you must initialize libpqtypes as a libpq EventProc. +The PQinitTypes function takes a PGconn that libpqtypes will be initialized +with; each PGconn requires its own initialization. + +PQclearTypes removes all registered types from the given PGconn. A good use +for this is after a PQresetXXX call when it might be desired to re-register +all types that may have gone stale. +.SH RETURN VALUE +.LP +PQinitTypes and PQclearTypes return zero if it fails and non-zero if it succeeds. +.SH EXAMPLES +.LP +.SS Initializing libpqtypes +The examples shows how to initialize libpqtypes with the libpq event system. +.RS +.nf +.LP +\fB/* call prior to any other libpqtypes functions that operate on conn. */ +PQinitTypes(conn); +\fP +.fi +.RE +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +None + + diff --git a/source/docs/man3/PQlocalTZInfo.3 b/source/docs/man3/PQlocalTZInfo.3 new file mode 100644 index 0000000..563a6ea --- /dev/null +++ b/source/docs/man3/PQlocalTZInfo.3 @@ -0,0 +1,66 @@ +.TH "PQlocalTZInfo" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQlocalTZInfo \- Gets the local machine\'s timezone information. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +void PQlocalTZInfo(time_t *\fIt\fP, int *\fIgmtoff\fP, int *\fIisdst\fP, char **\fItzabbrp\fP); +\fP +.SH DESCRIPTION +.LP +This function retrieves the local machine\'s timezone information. + +If the \fIt\fP argument is not NULL, it represents a time_t value to get timezone +information for. If it is NULL, the current time is used. + +The \fIgmtoff\fP argument will be pointed at the number of seconds from GMT, +same value as the GNU (struct tm).tm_gmtoff extension. + +The \fIisdst\fP argument will be pointed at zero if in standard time, one if in +daylight savings time and negative one if unknown. + +The \fItzabbrp\fP argument will be pointed at the timezone abbreviation, like +PST, ADT, EST, etc.. +.SH RETURN VALUE +.LP +None. +.SH EXAMPLES +.LP +This example gets the local timezone information for a file\'s modified time. +.LP +.RS +.nf +\fBint gmtoff; +int isdst; +char *tzabbr; +struct stat st; + +stat("report.xml", &st); +PQlocalTZInfo(&st.st_mtime, &gmtoff, &isdst, &tzabbr); +\fP +.fi +.RE +.SH RATIONALE +.LP +libpqtypes needs the ability to get the local machine\'s timezone information for the +datetime data types. It later became apparent that a portable way of getting timezone +information was very useful and in demand. Thus, this function was made public. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIpqt-specs(3)\fP diff --git a/source/docs/man3/PQparamClear.3 b/source/docs/man3/PQparamClear.3 new file mode 100644 index 0000000..b26b4b0 --- /dev/null +++ b/source/docs/man3/PQparamClear.3 @@ -0,0 +1 @@ +.so man3/PQparamCreate.3 \ No newline at end of file diff --git a/source/docs/man3/PQparamCount.3 b/source/docs/man3/PQparamCount.3 new file mode 100644 index 0000000..b26b4b0 --- /dev/null +++ b/source/docs/man3/PQparamCount.3 @@ -0,0 +1 @@ +.so man3/PQparamCreate.3 \ No newline at end of file diff --git a/source/docs/man3/PQparamCreate.3 b/source/docs/man3/PQparamCreate.3 new file mode 100644 index 0000000..ba0b6f7 --- /dev/null +++ b/source/docs/man3/PQparamCreate.3 @@ -0,0 +1,76 @@ +.TH "PQparamCreate" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQparamCreate, PQparamCount PQparamReset, PQparamClear \- PGparam management functions. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +PGparam *PQparamCreate(const PGconn *\fIconn\fP); +.br +PGparam *PQparamDup(PGparam *\fIparam\fP); +.br +int PQparamCount(PGparam *\fIparam\fP); +.br +void PQparamReset(PGparam *\fIparam\fP); +.br +void PQparamClear(PGparam *\fIparam\fP); +\fP +.SH DESCRIPTION +.LP +These functions manage the opaque PGparam object. + +\fIPQparamCreate\fP() will allocate and initialize a new PGparam object. +After the create call, the PGparam object is ready for use. WARNING: Only +types that have been registered via PQregisterXXX, will be available +to the param. Meaning, the param is not updated with types registered +after the param is created. + +\fIPQparamDup\fP() will duplicate a given PGparam, including any internal +values that have already been put. This is useful in cases where you want +to queue qeuries to execute at a later time, like a connection pooler. The +problem is PGparamCreate requires a connection object, which may not be +available when attempting to enqueue a query with its PGparam object. +Instead, a PGparam object can used for the sole purpose of creating +duplicates while there are no available PGconn objects. + +\fIPQparamCount\fP() gets the number of parameters in a PGparam object. + +\fIPQparamReset\fP() will clear out any previously put parameters, but will +not free any memory. This is useful for application looking to "reuse" a +PGparam object. + +\fIPQparamClear\fP() releases all resources being used by a PGparam object, +the object should not be used after a clear. + +It is very important to call \fIPQparamReset\fP() if you plan on reusing a +PGparam object. +.SH RETURN VALUE +.LP +\fIPQparamCreate\fP() returns a pointer to a PGparam object on success and NULL +if something failed (check \fIPQgeterror(3)\fP for more information). + +\fIPQparamCount\fP() returns the number of parameters in a PGparam object. + +\fIPQparamReset\fP() and \fIPQparamClear\fP() have no return values. If +either function is provided a NULL PGparam pointer, it will silently fail. +.SH EXAMPLES +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIpqt-specs(3)\fP, \fIPQputf(3)\fP, \fIPQgeterror(3)\fP, \fIPQparamExec(3)\fP diff --git a/source/docs/man3/PQparamDup.3 b/source/docs/man3/PQparamDup.3 new file mode 100644 index 0000000..6839531 --- /dev/null +++ b/source/docs/man3/PQparamDup.3 @@ -0,0 +1 @@ +.so man3/PQparamCreate.3 diff --git a/source/docs/man3/PQparamExec.3 b/source/docs/man3/PQparamExec.3 new file mode 100644 index 0000000..861b3cc --- /dev/null +++ b/source/docs/man3/PQparamExec.3 @@ -0,0 +1,106 @@ +.TH "PQparamExec" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQparamExec, PQparamExecPrepared \- Executes a paramertized query using the parameters in a PGparam. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +PGresult *PQparamExec(PGconn *\fIconn\fP, PGparam *\fIparam\fP, +.br + const char *\fIcommand\fP, int \fIresultFormat\fP); +.br +PGresult *PQparamExecPrepared(PGconn *\fIconn\fP, PGparam *\fIparam\fP, +.br + const char *\fIstmtName\fP, int \fIresultFormat\fP); +\fP +.SH DESCRIPTION +.LP +The \fIPQparamExec\fP() and \fIPQparamExecPrepared\fP() functions execute +a parameterized query using the parameters in a PGparam. The only difference +between these functions is that \fIPQparamExec\fP() expects a parameterized +\fIcommand\fP string while \fIPQparamExecPrepared\fP() expects a \fIstmtName\fP +previously prepared via PQprepare(). + +Both functions take a \fIparam\fP argument, which must contain the same number +of parameters as either the \fIcommand\fP string or previously prepared \fIstmtName\fP. +Internally, the \fIparam\fP is transformed into parallel arrays that are +supplied to a PQexecParams() or PQexecPrepared() call. + +The \fIresultFormat\fP argument indicates if text or binary results are desired; +a value of zero or one respectively. \fIPQgetf\fP supports both text and binary +result formats, with the exclusion of arrays and composites which only support binary. +.SH RETURN VALUE +.LP +On success, a pointer to a PGresult is returned. On error, NULL is +returned and \fIPQgeterror(3)\fP will contain an error message. + +\fBIMPORTANT!\fP +.br +There is a difference in behavior between \fIPQparamExec\fP() and \fIPQparamExecPrepared\fP() +and the libpq functions they wrap, PQexecParams() and PQexecPrepared(). +\fIPQparamExec\fP() and \fIPQparamExecPrepared\fP() only return a non-NULL +PGresult when the result status is either PGRES_COMMAND_OK, PGRES_TUPLES_OK or +PGRES_EMPTY_QUERY. If these functions detect any other result status, the +PGresult is cleared and a NULL result is returned. Before clearing the PGresult +and returning NULL, these functions first copy the result error message into the +libpqtypes error system, accessible via \fIPQgeterror(3)\fP. This allows applications +to get a result\'s error message without needing the result object. \fIconn\fP error +messages are also copied to the libpqtypes error system. + +This behavior difference provides a single error indicator, a NULL return, and a +single function that can get the error message, \fIPQgeterror\fP(). +.SH EXAMPLES +.LP +.SS Using PQparamExec +The example uses \fIPQparamExec\fP() to execute a query using a PGparam. +The example also demonstrates how to detect a failed exec and output an error +message. +.RS +.nf +.LP +\fBPGparam *param = PQparamCreate(conn); + +if(!PQputf(param, "%text %int4", "ACTIVE", CAT_CAR)) +{ + fprintf(stderr, "PQputf: %s\\n", PQgeterror()); +} +else +{ + PGresult *res = PQparamExec(conn, param, + "SELECT * FROM t WHERE status=$1 AND category=$2", 1); + + if(!res) + fprintf(stderr, "PQparamExec: %s\\n", PQgeterror()); + else + print_results(res); + + PQclear(res); +} + +PQparamClear(param); +\fP +.fi +.RE +.SS Using PQparamExecPrepared +\fIPQparamExecPrepared\fP() is behaves identically to \fIPQparamExec\fP(), except +\fIPQparamExecPrepared\fP() requires that a statement has been previously prepared +via PQprepare(). Also, a \fIstmtName\fP is supplied rather than a parameterized +\fIcommand\fP string. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIPQparamCreate(3)\fP, \fIPQparamSendQuery(3)\fP, \fIPQparamSendQueryPrepared(3)\fP diff --git a/source/docs/man3/PQparamExecPrepared.3 b/source/docs/man3/PQparamExecPrepared.3 new file mode 100644 index 0000000..c5f975f --- /dev/null +++ b/source/docs/man3/PQparamExecPrepared.3 @@ -0,0 +1 @@ +.so man3/PQparamExec.3 \ No newline at end of file diff --git a/source/docs/man3/PQparamReset.3 b/source/docs/man3/PQparamReset.3 new file mode 100644 index 0000000..b26b4b0 --- /dev/null +++ b/source/docs/man3/PQparamReset.3 @@ -0,0 +1 @@ +.so man3/PQparamCreate.3 \ No newline at end of file diff --git a/source/docs/man3/PQparamSendQuery.3 b/source/docs/man3/PQparamSendQuery.3 new file mode 100644 index 0000000..23e3ab7 --- /dev/null +++ b/source/docs/man3/PQparamSendQuery.3 @@ -0,0 +1,89 @@ +.TH "PQparamSendQuery" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQparamSendQuery, PQparamSendQueryPrepared \- Executes an asynchronous paramertized query using the parameters in a PGparam. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +int PQparamSendQuery(PGconn *\fIconn\fP, PGparam *\fIparam\fP, +.br + const char *\fIcommand\fP, int \fIresultFormat\fP); +.br +int PQparamSendQueryPrepared(PGconn *\fIconn\fP, PGparam *\fIparam\fP, +.br + const char *\fIstmtName\fP, int \fIresultFormat\fP); +\fP +.SH DESCRIPTION +.LP +The \fIPQparamSendQuery\fP() and \fIPQparamSendQueryPrepared\fP() functions execute +an asynchronous paramertized query using the parameters in a PGparam. The only difference +between these functions is that \fIPQparamSendQuery\fP() expects a parameterized +\fIcommand\fP string while \fIPQparamSendQueryPrepared\fP() expects a \fIstmtName\fP +previously prepared via PQprepare(). + +Both functions take a \fIparam\fP argument, which must contain the same number +of parameters as either the \fIcommand\fP string or previously prepared \fIstmtName\fP. +Internally, the \fIparam\fP is transformed into parallel arrays that are +supplied to a PQsendQueryParams() or PQsendQueryPrepared() call. + +The \fIresultFormat\fP argument indicates if text or binary results are desired; +a value of zero or one respectively. \fIPQgetf\fP supports both text and binary +result formats, with the exclusion of arrays and composites which only support binary. + +After successfully calling \fIPQparamSendQuery\fP() or \fIPQparamSendQueryPrepared\fP(), +call libpq\'s PQgetResult() one or more times to obtain the results. +.SH RETURN VALUE +.LP +On success, a non-zero value is returned. On error, zero is +returned and \fIPQgeterror(3)\fP will contain an error message. +.SH EXAMPLES +.LP +.SS Using PQparamSendQuery +The example uses \fIPQparamSendQuery\fP() to execute a query using a PGparam. +.RS +.nf +.LP +\fBPGparam *param = PQparamCreate(conn); + +if(!PQputf(param, "%text %int4", "ACTIVE", CAT_CAR)) +{ + fprintf(stderr, "PQputf: %s\\n", PQgeterror()); +} +else +{ + int success = PQparamSendQuery(conn, param, + "SELECT * FROM t WHERE status=$1 AND category=$2", 1); + + if(!success) + fprintf(stderr, "PQparamSendQuery: %s\\n", PQgeterror()); + else + get_and_print_results(conn); /* calls PQgetResult() */ +} + +PQparamClear(param); +\fP +.fi +.RE +.SS Using PQparamSendQueryPrepared +\fIPQparamSendQueryPrepared\fP() is behaves identically to \fIPQparamSendQuery\fP(), except +\fIPQparamSendQueryPrepared\fP() requires that a statement has been previously prepared +via PQprepare(). Also, a \fIstmtName\fP is supplied rather than a parameterized +\fIcommand\fP string. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIPQparamCreate(3)\fP, \fIPQgeterror(3)\fP, \fIPQparamExec(3)\fP, \fIPQparamExecPrepared(3)\fP diff --git a/source/docs/man3/PQparamSendQueryPrepared.3 b/source/docs/man3/PQparamSendQueryPrepared.3 new file mode 100644 index 0000000..53e207b --- /dev/null +++ b/source/docs/man3/PQparamSendQueryPrepared.3 @@ -0,0 +1 @@ +.so man3/PQparamSendQuery.3 \ No newline at end of file diff --git a/source/docs/man3/PQputf.3 b/source/docs/man3/PQputf.3 new file mode 100644 index 0000000..999c986 --- /dev/null +++ b/source/docs/man3/PQputf.3 @@ -0,0 +1,120 @@ +.TH "PQputf" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQputf, PQputvf \- Packs a set of parameters in a PGparam for use with a parameterized query. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +int PQputf(PGparam *\fIparam\fP, const char *\fIformat\fP, ...); +.br +int PQputvf(PGparam *\fIparam\fP, char *\fIstmtBuf\fP, size_t \fIstmtBufLen\fP, +.br + const char *\fIformat\fP, va_list \fIap\fP); +\fP +.SH DESCRIPTION +.LP +The \fIPQputf\fP() and \fIPQputvf\fP() functions put one or more query +parameters into a PGparam using a printf style interface. Any number +of parameters can be put at the same time. + +The \fIformat\fP argument is a data type specifier string indicating the +parameters being put, such as "%int4 %polygon". \fIformat\fP cannot be +NULL or an empty string. The variable argument list must match the +\fIformat\fP, either "..." or \fIap\fP. The number of arguments required +for each data type is dependant on the data type itself; built-in +PostgreSQL types always require a single argument. + +The \fIPQputvf\fP() function can construct a parameterized command string +from \fIformat\fP, as long as \fIstmtBuf\fP and \fIstmtBufLen\fP have been +provided. If the construction of \fIstmtBuf\fP is not desired, set it +to NULL and set \fIstmtBufLen\fP to 0. When a constructed statement is desired, +the contents of \fIformat\fP will be copied to \fIstmtBuf\fP and +all data type specifiers, like "%int4", will be replaced with $1, $2, etc... +syntax. The result is a parameterized statement in synch with \fIparam\fP +and ready to be executed. For instance: if \fIspec\fP is "SELECT %int4 + %int4", +the resulting \fIstmtBuf\fP would be "SELECT $1 + $2". +.SH RETURN VALUE +.LP +On success, a non-zero value is returned. On error, zero is +returned and \fIPQgeterror(3)\fP will contain an error message. + +If any put operation fails, the \fIparam\fP is reverted back to the number +of parameters it had prior to the function call; partial puts are not allowed. +.SH EXAMPLES +.LP +.SS Using PQputf +The example uses \fIPQputf\fP() to put a few parameters into a PGparam. Note +that the last parameter puts an SQL NULL as an int4 using the pass-by-pointer +type-specifier pointer flag '*', rather than pass-by-value. +.RS +.nf +.LP +\fBPGtext text = "foobar"; +PGint8 i8 = PQT_INT64CONST(1099511627776); +PGint4 *i4ptr = NULL; +PGparam *param = PQparamCreate(conn); + +if(!PQputf(param, "%text %int8 %int4*", text, i8, i4ptr)) + fprintf(stderr, "*ERROR: %s\\n", PQgeterror()); + +PQparamClear(param); +\fP +.fi +.RE +.SS Using PQputvf +The example creates an application function named execf. execf is a +wrapper to \fIPQputvf\fP(), as well as \fIPQparamExec(3)\fP. It is +similar to \fIPQexecf\fP(). The only difference is that the PQexecf +implementation uses a smaller stack buffer and allocates heap memory +when needed. +.RS +.nf +.LP +\fBPGresult * +execf(PGconn *conn, const char *format, ...) +{ + va_list ap; + char stmt[32768]; + PGparam *param; + PGresult *res = NULL; + + /* create the temporary PGparam */ + if(!(param = PQparamCreate(conn))) + return NULL; /* PQseterror already called */ + + /* put the params, create the stmt and exec it */ + va_start(ap, format); + if(PQputvf(param, stmt, sizeof(stmt), format, ap)) + res = PQparamExec(conn, param, stmt, 1); // resfmt is binary + va_end(ap); + + /* param must be cleared */ + PQparamClear(param); + return res; +} + +/* Example: execf will put 2 ints and execute "SELECT $1 + $2" */ +PGresult *res = execf(conn, "SELECT %int4 + %int4", 100, 67); +if(!res) + fprintf(stderr, "*ERROR: %s\\n", PQgeterror()); +\fP +.fi +.RE +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIpqt-specs(3)\fP, \fIPQparamCreate(3)\fP, \fIPQgeterror(3)\fP, \fIPQseterror(3)\fP diff --git a/source/docs/man3/PQputvf.3 b/source/docs/man3/PQputvf.3 new file mode 100644 index 0000000..f8762aa --- /dev/null +++ b/source/docs/man3/PQputvf.3 @@ -0,0 +1 @@ +.so man3/PQputf.3 diff --git a/source/docs/man3/PQregisterComposites.3 b/source/docs/man3/PQregisterComposites.3 new file mode 100644 index 0000000..3967201 --- /dev/null +++ b/source/docs/man3/PQregisterComposites.3 @@ -0,0 +1,80 @@ +.TH "PQregisterComposites" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQregisterComposites \- Registers a composites. +.SH SYNOPSIS +.LP +\fB#include +.br +int PQregisterComposites(PGconn *\fIconn\fP, PGregisterType *\fItypes\fP, +.br + int \fIcount\fP); +\fP +.SH DEPRECATED +.LP +THIS FUNCTION IS DEPRECATED. New applications should use PQregisterTypes. +This function is now a wrapper to PQregisterTypes. +.SH DESCRIPTION +.LP +The \fIPQregisterComposites\fP() function allows an application +to register one or more composites. This function can be +called as many times as an application needs on a PGconn. + +This function must execute a query against the backend to retrieve type +information for each composite, thus this should not be called from within +a transaction. It is recommended to register multiple composites at +the same time to avoid round trip overhead. + +The \fItypes\fP argument is an array containing \fIcount\fP composite types +to register. Composites do not use the typput or typget members of the +PGregisterType structure, thus these memebrs are ignored. If any composite +does not exist, the register is aborted. + +NOTE: The typname member of the PGregisterType structure can optionally +contain the type's schema: schema.typname. + +WARNING: \fIPQparamCreate\fP is only aware of types that have already been +registered. If you need to put a composite into a param, make sure it is first +registered. + +.SH EXAMPLES +.LP +.SS Using PQregisterComposites +The example registers two composite types. +.RS +.nf +.LP +\fBPGregisterType comp_types[] = { + {"myschema.simple", NULL, NULL}, + {"complex", NULL, NULL} +}; + +if (!PQregisterComposites(conn, comp_types, 2)) + fprintf(stderr, "PQregisterComposites: %s\\n", PQgeterror()); +\fP +.fi +.RE +.SH RETURN VALUE +.LP +On success, a non-zero value is returned. On error, zero is +returned and \fIPQgeterror(3)\fP will contain an error message. +.SH EXAMPLES +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIpqt-handlers(3)\fP, \fIPQputf(3)\fP, \fIPQgetf(3)\fP + diff --git a/source/docs/man3/PQregisterResult.3 b/source/docs/man3/PQregisterResult.3 new file mode 100644 index 0000000..2987e11 --- /dev/null +++ b/source/docs/man3/PQregisterResult.3 @@ -0,0 +1,122 @@ +.TH "PQregisterResult" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQregisterResult \- Registers sub-classes, composites and user-defined types found within a PGresult. +.SH SYNOPSIS +.LP +\fB#include +.br +int PQregisterResult(PGconn *\fIconn\fP, int \fIwhich\fP, PGregisterType *\fItypes\fP, +.br + int \fIcount\fP, PGresult *\fIres\fP); +\fP +.SH DESCRIPTION +.LP +The \fIPQregisterResult\fP() function registers the types found within +a given PGresult, thus this function makes no calls to a PostgreSQL server +since the result data is already available. + +The \fIwhich\fP argument can be PQT_COMPOSITE or PQT_USERDEFINED, +but not PQT_SUBCLASS. The only reason being sub-classes don't talk to +the server so they have no result set. + +The \fItypes\fP argument is an array containing \fIcount\fP types +to register. This array must be identical to what was provided to +the originating PQregisterTypes call. + +The \fIres\fP argument is a PGresult normally created by calling +PQregisterTypes followed by PQgetResult. However, it is possible to +create your own result via PQmakeEmptyPGresult, PQsetResultAttrs, +PQsetvalue and call this function. This approach is a bit risky being +how the result set generated by type lookup queries are internal and +subject to change. + +.SH EXAMPLES +.LP +.SS Using PQregisterResult +The example registers two composite types asynchronously. It is worth +noting that the PGresult obtained via PQgetResult can be cached by an +application and used when creating new connections, as a way to avoid +repeatedly performing type lookups with the server. +.RS +.nf +.LP +\fBPGregisterType comp_types[] = { + {"myschema.simple", NULL, NULL}, + {"complex", NULL, NULL} +}; + +/* asynchronous registration */ +if (PQregisterTypes(conn, PQT_COMPOSITE, comp_types, 2, 1)) +{ + /* example of a typical event loop */ + for(;;) + { + int n; + fd_set set; + int fd = PQsocket(conn); + struct timeval tv = {0, 500000}; + + FD_ZERO(&set); + FD_SET(fd, &set); + n = select(fd + 1, &set, NULL, NULL, &tv); //or kqueue,epoll,poll,etc.. + + if (n == -1) + { + //error + } + else if (n == 0) + { + //timeout, do other work .... + } + else + { + PGresult *res; + + PQconsumeInput(conn); + if(!PQisBusy(conn)) + { + /* done */ + if(!(res = PQgetResult(conn))) + break; + + n = PQregisterResult(conn, PQT_COMPOSITE, comp_types, 2, res); + + /* This could also be cached and reused with PQregisterResult */ + PQclear(res); + + if (!n) + //error, consult PQgeterror() + } + } + } +} +\fP +.fi +.RE +.SH RETURN VALUE +.LP +On success, a non-zero value is returned. On error, zero is +returned and \fIPQgeterror(3)\fP will contain an error message. +.SH EXAMPLES +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIPQregisterTypes(3)\fP, \fIpqt-handlers(3)\fP, \fIPQputf(3)\fP, \fIPQgetf(3)\fP + + + diff --git a/source/docs/man3/PQregisterSubClasses.3 b/source/docs/man3/PQregisterSubClasses.3 new file mode 100644 index 0000000..8570c47 --- /dev/null +++ b/source/docs/man3/PQregisterSubClasses.3 @@ -0,0 +1,62 @@ +.TH "PQregisterSubClasses" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQregisterSubClasses \- Registers a type aliases or sub-classes. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +int PQregisterSubClasses(PGconn *\fIconn\fP, PGregisterType *\fItypes\fP, +.br + int \fIcount\fP); +\fP +.SH DEPRECATED +.LP +THIS FUNCTION IS DEPRECATED. New applications should use PQregisterTypes. +This function is now a wrapper to PQregisterTypes. +.SH DESCRIPTION +.LP +The \fIPQregisterSubClasses\fP() function allows an application +to register an alias or sub-class of another type. + +The \fItypes\fP argument is an array containing \fIcount\fP sub class types +to register. The typname member of the PGregisterType structure must +specify an inheritence relationship: ex. "myint=int4" where myint inherits +from int4. The \'=\' is called the inheritence operator. If both typput +and typget members of the PGregisterType structure are NULL, the type at +that element will behave identically to the type it is inheriting +from; an alias. Otherwise, the base type's put and/or get routines will +be overridden. + +NOTE: The typname member of the PGregisterType structure can optionally +contain the type's schema: schema.typname. + +WARNING: \fIPQparamCreate\fP is only aware of types that have already been +registered. If you need to put a type into a param, make sure it is first +registered. + +.SH RETURN VALUE +.LP +On success, a non-zero value is returned. On error, zero is +returned and \fIPQgeterror(3)\fP will contain an error message. +.SH EXAMPLES +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIpqt-handlers(3)\fP, \fIPQputf(3)\fP, \fIPQgetf(3)\fP + diff --git a/source/docs/man3/PQregisterTypes.3 b/source/docs/man3/PQregisterTypes.3 new file mode 100644 index 0000000..0c09931 --- /dev/null +++ b/source/docs/man3/PQregisterTypes.3 @@ -0,0 +1,90 @@ +.TH "PQregisterTypes" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQregisterTypes \- Registers sub-classes, composites and user-defined types. +.SH SYNOPSIS +.LP +\fB#include +.br +int PQregisterTypes(PGconn *\fIconn\fP, int \fIwhich\fP, PGregisterType *\fItypes\fP, +.br + int \fIcount\fP, int \fIasync\fP); +\fP +.SH DESCRIPTION +.LP +The \fIPQregisterTypes\fP() function allows an application +to register one or more PQT_SUBCLASS, PQT_COMPOSITE or PQT_USERDEFINED +types which is defined by the \fIwhich\fP argument. + +When \fIwhich\fP is PQT_COMPOSITE or PQT_USERDEFINED, this function must +execute a query against the backend to retrieve type information, thus +this should not be called from within a transaction. It is recommended to +register multiple composites at the same time to avoid round trip overhead. + +The \fItypes\fP argument is an array containing \fIcount\fP types +to register. Composites do not use the typput or typget members of the +PGregisterType structure, thus these memebrs are ignored. If any composite +does not exist, the register is aborted. User-defined types must set either +typput and/or typget for each type. Sub-classes typname member of the +PGregisterType structure must specify an inheritence relationship: +ex. "myint=int4" where myint inherits from int4. The \'=\' is called the +inheritence operator. If both typput and typget members of the PGregisterType +structure are NULL, the type at that element will behave identically to the +type it is inheriting from; an alias. Otherwise, the base type's put and/or +get routines will be overridden. + +The \fIasync\fP argument indicates if the asynchronous type registration +should be used. When non-zero, the caller must proceed in the normal +async libpq fashion (PQconsumeInput, PQisBusy, PQgetResult) and provide a +PGresult to PQregisterResult when obtained to complete the registration. + +NOTE: The typname member of the PGregisterType structure can optionally +contain the type's schema: schema.typname. + +WARNING: \fIPQparamCreate\fP is only aware of types that have already been +registered. If you need to put a type into a param, make sure it is first +registered. + +.SH EXAMPLES +.LP +.SS Using PQregisterTypes +The example registers two composite types. +.RS +.nf +.LP +\fBPGregisterType comp_types[] = { + {"myschema.simple", NULL, NULL}, + {"complex", NULL, NULL} +}; + +if (!PQregisterTypes(conn, PQT_COMPOSITE, comp_types, 2, 0)) + fprintf(stderr, "PQregisterTypes: %s\\n", PQgeterror()); +\fP +.fi +.RE +.SH RETURN VALUE +.LP +On success, both PQregisterTypes returns a non-zero value. +On error, zero is returned and \fIPQgeterror(3)\fP will contain an +error message. +.SH EXAMPLES +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIPQregisterResult(3)\fP, \fIpqt-handlers(3)\fP, \fIPQputf(3)\fP, \fIPQgetf(3)\fP + + diff --git a/source/docs/man3/PQregisterUserDefinedTypes.3 b/source/docs/man3/PQregisterUserDefinedTypes.3 new file mode 100644 index 0000000..f728c79 --- /dev/null +++ b/source/docs/man3/PQregisterUserDefinedTypes.3 @@ -0,0 +1,82 @@ +.TH "PQregisterUserDefinedTypes" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQregisterUserDefinedTypes \- Registers a user-defined types. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +int PQregisterUserDefinedTypes(PGconn *\fIconn\fP, PGregisterType *\fItypes\fP, +.br + int \fIcount\fP); +\fP +.SH DEPRECATED +.LP +THIS FUNCTION IS DEPRECATED. New applications should use PQregisterTypes. +This function is now a wrapper to PQregisterTypes. +.SH DESCRIPTION +.LP +The \fIPQregisterUserDefinedTypes\fP() function allows an application +to register one or more user-defined types at runtime. User-defined types +are custom types in a backend that implement their own C procedures for +in/out/send/recv. + +This function must execute a query against the backend to retrieve type +information for each user-defined type, thus this should not be called +from within a transaction. It is recommended to register multiple types at +the same time to avoid round trip overhead. + +The \fItypes\fP argument is an array containing \fIcount\fP user-defined +types to register. If any type does not exist, the register +is aborted. Either typput and/or typget must be specified for each type +in the \fItypes\fP array. + +NOTE: The typname member of the PGregisterType structure can optionally +contain the type's schema: schema.typname. + +WARNING: \fIPQparamCreate\fP is only aware of types that have already been +registered. If you need to put a type into a param, make sure it is first +registered. + +\fBUser-defined Types Registration\fP +.br +This example registers two user-defined types. +.nf +.RS +.LP +\fBPGregisterType types[] = { + {"graphics.rgb", rgb_put, rgb_get}, + {"graphics.digon", digon_put, digon_get} +}; + +if (!PQregisterUserDefinedTypes(conn, types, 2)) + fprintf(stderr, "PQregisterUserDefinedTypes: %s\\n", PQgeterror()); +\fP +.RE +.fi + +.SH RETURN VALUE +.LP +On success, a non-zero value is returned. On error, zero is +returned and \fIPQgeterror(3)\fP will contain an error message. +.SH EXAMPLES +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIpqt-handlers(3)\fP, \fIPQputf(3)\fP, \fIPQgetf(3)\fP + diff --git a/source/docs/man3/PQsendf.3 b/source/docs/man3/PQsendf.3 new file mode 100644 index 0000000..77c4e1e --- /dev/null +++ b/source/docs/man3/PQsendf.3 @@ -0,0 +1 @@ +.so man3/PQexecf.3 \ No newline at end of file diff --git a/source/docs/man3/PQsendvf.3 b/source/docs/man3/PQsendvf.3 new file mode 100644 index 0000000..77c4e1e --- /dev/null +++ b/source/docs/man3/PQsendvf.3 @@ -0,0 +1 @@ +.so man3/PQexecf.3 \ No newline at end of file diff --git a/source/docs/man3/PQseterror.3 b/source/docs/man3/PQseterror.3 new file mode 100644 index 0000000..e1ca3a1 --- /dev/null +++ b/source/docs/man3/PQseterror.3 @@ -0,0 +1 @@ +.so man3/PQgeterror.3 \ No newline at end of file diff --git a/source/docs/man3/PQspecPrepare.3 b/source/docs/man3/PQspecPrepare.3 new file mode 100644 index 0000000..1fb4ac0 --- /dev/null +++ b/source/docs/man3/PQspecPrepare.3 @@ -0,0 +1,123 @@ +.TH "PQspecPrepare" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQspecPrepare \- Prepares a libpqtypes specifier format string. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +int PQspecPrepare(PGconn *\fIconn\fP, const char *\fIname\fP, +.br + const char *\fIformat\fP, int \fIis_stmt\fP); +.br +void PQclearSpecs(PGconn *\fIconn\fP); +\fP +.SH DESCRIPTION +.LP +PQspecPrepare allows an application to prepare specifier format strings +that will be used frequently. By preparing a specifier format string, +one avoids the parsing and type handler lookup costs. This becomes a +significant win when managing large result sets or arrays, where the +specifier format, like "%int4 %text %bytea", must be prepared for each +tuple/element. + +As with PQregisterXXX, only specifier format strings prepared prior +to the creation of a PGresult or PGparam, will be available for use. This is +because the prepared type spec is cached within a PGconn object and copied +to all subsequent PGparam and PGresult objects. + +Every prepared type spec is given a \fIname\fP, which is used as its unique identifier. +To use a prepared type spec, the \fIname\fP is provided where ever a regular +specifier format string is allowed, like PQputf or PQgetf. The \fIname\fP must +be proceeded by a \'@\' AT sign. For more information about the syntax, +see the \fIpqt-specs(3)\fP man page. + +The \fIformat\fP argument is the specifier format string being prepared. When +this is NULL, the \fIname\fP prepared type spec is removed from the PGconn\'s +internal array. + +The \fIis_stmt\fP argument indicates if a parementerized statement version of +\fIformat\fP should be cached along with the prepared type spec. This means +all type specifiers in \fIformat\fP, like "%int4", will be converted to "$1" +syntax. When is_stmt is non-zero, a statement will created and cached. +For more information on specifer format string to paremterized statements, see +the \fIPQputf(3)\fP man page. NOTE: to use a prepared type spec with +execution functions like PQexecf, \fIis_stmt\fP must be set to non-zero. + +PQclearSpecs removes all prepared specifiers from the given PGconn, as +opposed to removing them one by one by setting PQspecPrepare's format +argument to NULL. A good use for this is after a PQresetXXX call when it +might be desired to re-prepare all type specifiers. + +Functions that support the use of a prepared type spec are: \fIPQputf\fP, +\fIPQputvf\fP, \fIPQgetf\fP, \fIPQgetvf\fP, \fIPQexecf\fP, \fIPQexecvf\fP, +\fIPQsendf\fP, \fIPQsendvf\fP, \fIPQparamExec\fP, \fIPQparamSendQuery\fP. + +\fBHINT:\fP A good rule of thumb for using prepared type specs, is when there +are a large number of PQputf/PQgetf calls per statement execution. This +commonly occurs when dealing with large result sets and arrays. +.SH RETURN VALUE +.LP +PQspecPrepare and PQclearSpecs return a nonzero value on success and +zero if it fails. Use PQgeterror() to get an error message. +.SH EXAMPLES +.LP +This example prepares a type spec and issues some PQputf calls. +.LP +.RS +.nf +\fBint i; +PQparam *param; + +if(!PQspecPrepare(conn, "prepared_spec", "%int4 %text", 0)) +{ + fprintf(stderr, "PQspecPrepare: %s\n", PQgeterror()); + exit(1); +} + +/* create after preparing spec */ +param = PQparamCreate(conn); + +for(i=0; i < 100000; i++) +{ + /* NOTE: nothing else can be in format string */ + PQputf(param, "@prepared_spec", 4, "text"); +} + +/* This elects to prepare a statement as well. After this returns, + * "SELECT myfunc($1, $2)" will be cached along with the prepared spec. + */ +PQspecPrepare(conn, "myfunc", "SELECT myfunc(%int4, %text)", 1); + +/* "myfunc" tells execf to execute "SELECT myfunc($1, $2)". If is_stmt + * was set to zero during the PQspecPrepare, the below would be invalid + * because execf doesn't know what to execute. + */ +PQexecf(conn, "@myfunc", 123, "text"); + +/* clear'm all */ +PQclearSpecs(conn); +\fP +.fi +.RE +.SH RATIONALE +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIpqt-specs(3)\fP, \fIPQgetf(3)\fP, \fIPQputf(3)\fP. + diff --git a/source/docs/man3/PQtypesRegister.3 b/source/docs/man3/PQtypesRegister.3 new file mode 100644 index 0000000..aea1b48 --- /dev/null +++ b/source/docs/man3/PQtypesRegister.3 @@ -0,0 +1,53 @@ +.TH PQtypesRegister 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +PQtypesRegister \- Registers libpqtypes with the libpq event system. +.SH SYNOPSIS +.LP +\fB#include +.br +.sp +int PQtypesRegister(PGconn *conn); +\fP +.SH DEPRECATED +.LP +THIS FUNCTION IS DEPRECATED. New applications should use PQinitTypes. +This function is now a wrapper to PQinitTypes. +.SH DESCRIPTION +.LP +libpqtypes makes use of the libpq Event System. Before using +libpqtypes, you must register libpqtypes as a libpq EventProc. +The function takes a PGconn that libpqtypes will be registered +with; each PGconn requires its own registration. +.SH RETURN VALUE +.LP +The function returns zero if it fails and non-zero if it succeeds. +.SH EXAMPLES +.LP +.SS Registering libpqtypes +The examples shows how to register libpqtypes with the libpq event system. +.RS +.nf +.LP +\fB/* call prior to any other libpqtypes functions that operate on conn. */ +PQtypesRegister(conn); +\fP +.fi +.RE +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +None + diff --git a/source/docs/man3/pqt-composites.3 b/source/docs/man3/pqt-composites.3 new file mode 100644 index 0000000..3d055b5 --- /dev/null +++ b/source/docs/man3/pqt-composites.3 @@ -0,0 +1,263 @@ +.TH "pqt-composites" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +pqt-composites \- A manual for libpqtypes composite handling. +.SH NOTE TO READER +.LP +Please read the \fIpqt-specs(3)\fP manual page prior to this document. +This document does not explain how to put or get data types. It +only describes how to put or get composites and composite arrays. +.SH DESCRIPTION +.LP +A composite is put using the PGparam structure. Each attribute of a +composite is put into a PGparam. When all attributes have been put, +the PGparam is put into another PGparam. Composites must be registered +on a per connection basis, \`man \fIpqt-handlers(3)\fP\'. + +To get a composite, a PGresult structure is used. Each composite +attribute is a field of the result. For non-array composites, there +is always only one tuple. + +Composites are only handled using binary format. This means that any type used +as a composite attribute must be put and gotten in binary format. If a +user-defined type does not implement a send and recv function in the backend, +it can not be used as a composite attribute. +.SS Simple Composite Example +.LP +This example demostrates the basics of putting and getting a composite type. +.nf +.RS +\fB +CREATE TYPE simple AS (a int4, t text); + +PGregisterType type = {"simple", NULL, NULL}; + +/* need to register the simple composite */ +PQregisterTypes(conn, PQT_COMPOSITE, &type, 1, 0); + +/* Composite attributes are put into PGparam structures */ +PGparam *simple = PQparamCreate(conn); + +/* put the simple composite attributes */ +PQputf(simple, "%int4 %text*", 45, "foobar"); + +/* Put an int4 and a simple composite */ +PGparam *param = PQparamCreate(conn); +PQputf(param, "%int4 %simple", 10, simple); +PQparamClear(simple); + +/* exec an insert */ +res = PQparamExec(conn, param, "INSERT INTO t VALUES($1,$2)", resfmt); +PQparamClear(param); + +/* ------------------------- + * Getting a composite + */ + +PGint4 i4; +PGtext textp; +char text[80]; +PGresult *simple; + +/* Get a simple composite, provide a ptr to a PGresult ptr. */ +PQgetf(result, 0, "%simple", 0, &simple); + +/* no longer needed */ +PQclear(result); + +/* Get the simple composite attributes from the simple result. + * Reference fields by name by using a '#' rather than a '%'. + * The field names are the composite attributes. + */ +PQgetf(simple, 0, "#int4 #text", "a", &i4, "t", &textp); +strcpy(text, textp); +PQclear(simple); +\fP +.RE +.nf + +In the above example, we used the \'#\' specifier mark to reference +fields by their name. The field names for a composite result object +are the composite attribute names. +.SS Nested Composite example: +.LP +The below example puts and gets a nested composite. The simple composite +is used as an attribute within the complex composite. +.nf +.RS +\fB +CREATE TYPE simple AS (a int4, t text) +CREATE TYPE complex AS (f8 float8, s simple); + +/* need to register simple and complex */ +PGregisterType types[] = { + {"simple", NULL, NULL}, + {"complex", NULL, NULL} +}; + +PQregisterTypes(conn, PQT_COMPOSITE, types, 2, 0); + +/* Composite attributes are put into PGparam structures */ +PGparam *simple = PQparamCreate(conn); +PGparam *complex = PQparamCreate(conn); + +/* put the simple composite attributes */ +PQputf(simple, "%int4 %text*", 45, "foobar"); + +/* put the complex composite attributes, which includes + * a nested composite. + */ +PQputf(complex, "%float8 %simple", 111.2223334, simple); + +/* no longer needed */ +PQparamClear(simple); + +/* Put an int4 and a complex composite */ +PGparam *param = PQparamCreate(conn); +PQputf(param, "%int4 %complex", 10, complex); +PQparamClear(complex); + +/* exec an insert */ +res = PQparamExec(conn, param, "INSERT INTO t VALUES($1,$2)", resfmt); +PQparamClear(param); + +/* ------------------------- + * Getting a nested composite + */ + +PGfloat8 f8; +PGint4 i4; +PGtext textp; +char text[80]; +PGresult *complex; +PGresult *simple; + +/* Get the complex composite, provide a ptr to a PGresult ptr. */ +PQgetf(result, 0, "%complex", 0, &complex); + +/* no longer needed */ +PQclear(result); + +/* Get the complex composite attributes from the complex result. + * Composite attributes are the result fields. When getting + * a single composite, non-array, only tuple 0 will exist. + * For the nested simple composite, we again provide a ptr to + * a PGresult ptr. + */ +PQgetf(complex, 0, "%float8 %simple", 0, &f8, 1, &simple); + +/* no longer needed */ +PQclear(complex); + +/* Get the simple composite attributes from the simple result. + * Reference fields by name by using a '#' rather than a '%'. + */ +PQgetf(simple, 0, "#int4 #text", "a", &i4, "t", &textp); +strcpy(text, textp); +PQclear(simple); +\fP +.RE +.nf +.SS An array of composites: +.LP +This example makes an array of complex composites. It builds +off the previous example. +.nf +.RS +\fB +int i; +PGarray complex_arr; +PGparam *simple = PQparamCreate(conn); +PGparam *complex = PQparamCreate(conn); + +complex_arr.ndims = 0; +complex_arr.param = PQparamCreate(conn); + +for(i=0; i < 100; i++) +{ + /* put the simple composite attributes */ + PQputf(simple, "%int4 %text*", 45, "foobar"); + + /* put the complex composite attributes, which includes + * a nested composite. + */ + PQputf(complex, "%float8 %simple", 111.2223334, simple); + + /* put the complex composite */ + PQputf(complex_arr.param, "%complex", complex); + + /* You must reset the simple and complex composites for + * the next loop iteration. + */ + PQparamReset(simple); + PQparamReset(complex); +} + +/* not needed anymore */ +PQparamClear(simple); +PQparamClear(complex); + +/* Put a complex composite array */ +PGparam *param = PQparamCreate(conn); +PQputf(param, "%complex[]", &complex_arr); +PQparamClear(complex_arr.param); + +/* exec an insert */ +res = PQparamExec(conn, param, "INSERT INTO t VALUES($1)", resfmt); +PQparamClear(param); + +/* ------------------------- + * Getting an array of composites + */ + +int i; +int ntups; +PGfloat8 f8; +PGint4 i4; +PGtext textp; +PGresult *simple; +PGarray complex_arr; + +/* Get the complex[], provide a ptr to a PGarray. */ +PQgetf(exec_result, 0, "%complex[]", 0, &complex_arr); + +/* no longer needed */ +PQclear(exec_result); + +ntups = PQntuples(complex_arr.res); +for(i=0; i < ntups; i++) +{ + PQgetf(complex_arr.res, i, "%float8 %simple", 0, &f8, 1, &simple); + + /* Nested composites are like any other composite, tuple 0! Unless, + * its a nested composite array. + */ + PQgetf(simple, 0, "#int4 #text", "a", &i4, "t", &textp); + + printf("(%f, (%d, %s))\\n", f8, i4, textp); + PQclear(simple); +} + +PQclear(complex_arr.res); +\fP +.RE +.nf +.SH EXAMPLES +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIPQgetf(3)\fP, \fIPQputf(3)\fP, \fIPQputvf(3)\fP diff --git a/source/docs/man3/pqt-handlers.3 b/source/docs/man3/pqt-handlers.3 new file mode 100644 index 0000000..ba2d275 --- /dev/null +++ b/source/docs/man3/pqt-handlers.3 @@ -0,0 +1,598 @@ +.TH "pqt-handlers" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +pqt-handlers \- A manual for implementing libpqtypes type handlers. +.SH DESCRIPTION +.LP +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. + +\fBType handlers have the below three properties:\fP +.LP +.RS +\fBType Specifier Name\fP +.br +A [schema].type name that will be used to reference your handler. +\`man \fIpqt-specs\fP\' for complete documentation on syntax. + +\fBPut Routine\fP +.br +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. + +\fBGet Routine\fP +.br +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. +.RE + +\fBPGregisterType\fP +.br +The \fIPGregisterType\fP 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. +.nf +.RS +\fB +typedef struct +{ + const char *typname; + PGtypeProc typput; + PGtypeProc typget; +} PGregisterType;\fP +.RE +.fi + +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. + +\fBPGtypeFormatInfo\fP +.br +The \fIPGtypeFormatInfo\fP structure provides useful connection-based information for +type handlers. For instance, your handler may have different implementations +depending on the server version .. \fIsversion\fP. +.nf +.RS +\fB +typedef struct +{ + int sversion; /* server version, e.g. 70401 for 7.4.1 */ + int pversion; /* FE/BE protocol version in use */ + char datestyle[32]; /* server\'s datestyle: like "SQL, MDY" */ + + /* When non-zero, server uses int64 timestamps */ + int integer_datetimes; +} PGtypeFormatInfo;\fP +.RE +.fi + +\fBPGrecordAttDesc\fP +.br +The \fIPGrecordAttDesc\fP structure defines the attributes of a composite. +Internally, libpqtypes keeps track of composite attributes using this structure. +.nf +.RS +\fB +typedef struct +{ + Oid attoid; /* Oid of the attribute */ + int attlen; /* storage size of attribute. -1 if not known */ + int atttypmod; /* The typmod of attribute. */ + char *attname; /* The name of the attribute. */ +} PGrecordAttDesc;\fP +.RE +.fi + +\fBPGtypeHandler\fP +.br +The \fIPGtypeHandler\fP structure represents all the properties of a +type handler. When a type is registered, this structure is used to catalog +the type\'s information. +.nf +.RS +\fB +typedef struct pg_typhandler +{ + /* An internal libpqtypes assigned id for this type handler. */ + int id; + + /* The schema name of this type, which may be empty if not + * provided during registration. + */ + char typschema[65]; + + /* The name of this type: like int2 or bytea, cannot be empty */ + char typname[65]; + + /* The storage size of this type. -1 if not known. */ + int typlen; + + /* The backend OID of the type. */ + Oid typoid; + + /* The backend array OID of the type. */ + Oid typoid_array; + + /* The put handler for this type. */ + PGtypeProc typput; + + /* The get handler for this type. */ + PGtypeProc typget; + + /* If this handler is a sub-class, this will be the \'id\' of + * the super class type handler. It is set to -1 if not + * a sub-class. + */ + int base_id; + + /* Indicates the number of composite attributes within the + * \'attDescs\' array. This is set to 0 for non-composites. + */ + int nattrs; + + /* If non-zero, the 'attDescs' pointer must be freed. */ + int freeAttDescs; + + /* The memory behind the 'attDescs' pointer when the number of + * attrs is less than 16. When greater than 16, heap memory + * is used and 'freeAttDescs' is set to a non-zero value. + */ + PGrecordAttDesc attDescsBuf[16]; + + /* An array of PGrecordAttDesc, one element per record + * attribute. Must be freed if 'freeAttDescs' is non-zero. + */ + PGrecordAttDesc *attDescs; +} PGtypeHandler; +\fP +.RE +.fi + +\fBPGtypeArgs\fP +.br +The \fIPGtypeArgs\fP structure is passed to all put and get handlers. It +contains all values needed by type handlers. +.nf +.RS +\fB +struct pg_typeargs +{ + /* Indicates if this is a put or get operation. */ + int is_put; + + /* Formatting information. */ + const PGtypeFormatInfo *fmtinfo; + + /* Indicates if a request for a direct pointer was + * made, %text*. + */ + int is_ptr; + + /* + * When \fIis_put\fP is non-zero, set this to 1 for binary and 0 for + * text format. It defaults to binary. When \fIis_put\fP is 0, this + * indicates the field type PQftype of \fIget.field_num\fP. + */ + int format; + + /* An argument list. Arguments should be retrieved with va_arg. */ + va_list ap; + + /* The position of this typname within a specifier + * string, 1-based. + */ + int typpos; + + /* Type handler for the specifier at typpos. */ + PGtypeHandler *typhandler; + + /* + * Report an error from within a handler. This error message + * will show up in PQgeterror. + * + * This always returns -1 so one can report an error and return + * -1 from a handler in a single statement: + * + * return args->errorf(args, "ERROR: %s", strerror(errno)); + * + * errorf always prepends a small header + * "schema.typname[pos:num] - msg". For example, if the above + * failed within the int4 handler and typpos was 5, the + * resulting error message would be: + * + * pg_catalog.int4[pos:5] - ERROR: Invalid argument + * + * errorf does not put any newlines in error message. + */ + int (*errorf)(PGtypeArgs *args, const char *format, ...); + + /* Used by type sub-class handlers. When \fIis_put\fP is + * non-zero, a sub-class prepares type data and then calls + * super. When \fIis_put\fP is zero, a sub-class first + * calls super to get the base class's deserialized value + * and can then convert it. + */ + int (*super)(PGtypeArgs *args, ...); + + /* This structure is used when \fIis_put\fP is non-zero. */ + struct + { + /* The PGparam structure passed to \fIPQputf\fP(). */ + PGparam *param; + + /* A buffer used to store the type's output format. If + * more than 'outl' bytes are needed, see 'expandBuffer'. + * Normally data is copied to the out buffer, but it can + * also be pointed elsewhere: like a const string or static + * memory. When repointing the out buffer, DO NOT use + * 'expandBuffer'. Never use realloc on this buffer. + */ + char *out; + + /* The size in bytes of the 'out' buffer. If expandBuffer + * is used, this will reflect the new buffer length. + */ + int outl; + + /* Expands the 'out' buffer to 'new_len'. If new_len is + * less than or equal to the current length 'outl', the + * expand request is ignored. This behaves just like a + * realloc, existing data is copied to the new memory. + * You should never use realloc on the out buffer. + * Returns -1 on error and 0 for success. + */ + int (*expandBuffer)(PGtypeArgs *args, int new_len); + + /* internal use only. */ + char *__allocated_out; + } put; + + /* This structure is used when \fIis_put\fP is zero. */ + struct + { + /* The PGresult passed to \fIPQgetf\fP(). + PGresult *result; + + /* The tuple number */ + int tup_num; + + /* the tuple field number. */ + int field_num; + } get; +};\fP +.RE +.fi + +.SH USER-DEFINED TYPES +.LP +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. + +.SS User-defined type example +.LP +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. +.nf +.RS +\fB +/* register the rgb type */ +PGregisterType type = {"graphics.rgb", rgb_put, rgb_get}; +PQregisterTypes(conn, PQT_USERDEFINED, &type, 1, 0); + +/* put an rgb */ +rgb_t rgb = {218, 218, 218}; +PGparam *param = PQparamCreate(conn); +PQputf(param, "%rgb", &rgb); + +/* get an rgb from tuple 0 field 4 */ +rgb_t rgb; +PQgetf(result, 0, "%graphics.rgb", 4, &rgb); + +/* ------------------------------- + * EXAMPLE RGB IMPLEMENTATION + */ + +#define hex2dec(v) (unsigned char)(((v) > '9') ? \ + ((v) - 'a') + 10 : (v) - '0') + +/* example rgb struct */ +typedef struct +{ + unsigned char r; + unsigned char b; + unsigned char g; +} rgb_t; + +/* RGB PGtypeProc handler - always puts in binary format */ +int rgb_put(PGtypeArgs *args) +{ + unsigned char *out; + rgb_t *rgb = va_arg(args->ap, rgb_t *); + + /* If rgb is NULL, put an SQL NULL value */ + if(!rgb) + { + args->put.out = NULL; + return 0; + } + + /* write the 3 bytes to the args out buffer */ + out = (unsigned char *)args->put.out; + *out++ = rgb->r; + *out++ = rgb->g; + *out = rgb->b; + return 3; /* number of bytes the server should expect */ +} + +/* RGB PGtypeProc handler */ +int rgb_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) + return args->errorf(args, "rgb* cannot be NULL"); + + /* text format: ex. \'#ff9966\' */ + if(PQfformat(args->format) == 0) + { + value++; /* skip the \'#\' 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]); + return 0; + } + + /* binary format */ + rgb->r = (unsigned char)value[0]; + rgb->g = (unsigned char)value[1]; + rgb->b = (unsigned char)value[2]; + return 0; +}\fP +.RE +.fi + +.SH TYPE SUB-CLASSING +.LP +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: +.nf +.RS +\fBPGregisterType type = {"myint4=pg_catalog.int4", NULL, NULL}; +PQregisterTypes(conn, PQT_SUBCLASS, &type, 1, 0);\fP +.RE +.fi + +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. + +\fBBENEFITS\fP + +1. Centralizes conversions from application data types to libpq data types +.br +2. Provides an easy all-inclusive interface for putting and getting values +.br +3. Allows applications to piggy-back off libpqtypes internal binary and text convertors +.br +4. Adds enormous flexiblity: (a few interesting ideas) +.br + -- %socket: sub-class the inet get routine and return a connected sockfd. +.br + -- %file: sub-class the text get routine and return a FILE* (text being a pathname) +.br + -- %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. + +.SS Sub-class example +.LP +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. + +\fB**NOTE:\fP %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. +.nf +.RS +\fB +/* we are going to register this under the \'pqt\' schema */ +PGregisterType type = {"pqt.epoch=pg_catalog.timestamptz", epoch_put, epoch_get}; +PQregisterTypes(conn, PQT_SUBCLASS, &type, 1, 0)) + +/* putting an epoch */ +struct stat st; +if(stat("/home/foobar/archive.tgz", &st) == 0) +{ + PGparam *param = PQparamCreate(conn); + PQputf(param, "%epoch", st.st_mtime); + //.... +} + +/* getting an epoch value, using fully qualified type name */ +struct utimbuf ut = {0, 0}; +PQgetf(result, tup_num, "%pqt.epoch", field_num, &ut.modtime); + +/* ------------------------------- + * EXAMPLE EPOCH SUB-CLASS IMPLEMENTATION + */ + +/* convert a time_t to a PGtimestamp and call args->super() */ +int epoch_put(PGtypeArgs *args) +{ + struct tm *tm; + PGtimestamp ts; + time_t t = va_arg(args->ap, time_t); + + tm = localtime(&t); + ts.date.isbc = 0; + ts.date.year = tm->tm_year + 1900; /* always 4-digit year */ + 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, this calls the base type\'s put routine + * (the super class). In this case, the super class + * expects a PGtimestamp as input. The super function + * returns whatever the base type\'s put routine returns + * (which for all puts is the byte count or -1 on error). + */ + return args->super(args, &ts); +} + +/* Calls args->super() to get a PGtimestamp and then converts + * it to a time_t value. + */ +int epoch_get(PGtypeArgs *args) +{ + PGtimestamp ts; + time_t *t = va_arg(args->ap, time_t *); + + if(!t) + return args->errorf(args, "time_t* cannot be NULL"); + + /* zero user bits */ + *t = 0; + + /* Internally, this calls the base type\'s get routine, + * which returns 0 or -1 on error. + */ + if(args->super(args, &ts) == -1) + return -1; /* args->errorf called by super already */ + + /* Since PGtimestamp contains an epoch member, we can + * just copy that value rather than calling mktime(). + */ + *t = (time_t)ts.epoch; + return 0; +} +\fP +.RE +.fi + +.SH COMPOSITES +.LP +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 \fIpqt-composites(3)\fP\' for a more information about composites. + +Registering a composite type: +.nf +.RS +\fB +CREATE TYPE simple AS (a int4, t text); +PGregisterType type = {"simple", NULL, NULL}; +PQregisterTypes(conn, PQT_COMPOSITE, &type, 1, 0);\fP +.RE +.fi + +*) The put and get routines must be NULL, composites cannot be sub-classed +.br +*) The provided name cannot resolve to the backend\'s RECORDOID +.br +*) The composite must exist at "conn" +.br +*) 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 +.br +*) Array Oid of the composite type +.br +*) Type len of the compsoite type, PQfsize + +For each composite attribute: + +*) Oid of the attribute +.br +*) Name of the attribute +.br +*) Type len of the attribute, PQfsize +.br +*) The typmod of the attribute, PQfmod + +.SH EXAMPLES +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIPQregisterTypes\fP(), \fIPQregisterResult\fP() diff --git a/source/docs/man3/pqt-specs.3 b/source/docs/man3/pqt-specs.3 new file mode 100644 index 0000000..5303d40 --- /dev/null +++ b/source/docs/man3/pqt-specs.3 @@ -0,0 +1,1221 @@ +.TH "pqt-specs" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +.SH NAME +pqt-specs \- A manual for libpqtypes data type specifier strings. +.SH DESCRIPTION +.LP +The type system allows addressing backend data types by their fully +qualified schema name. Similar to the backend type system, libpqtypes has +an input and output function for each type: put and get respectively. +All builtin types are supported by libpqtypes. Additional types, such as +composites and user-defined types, can be registered by an API-user +on a per connection basis. + +Putting and getting types are addressed by their backend names as +printf-style format specifiers. For instance: %int4, %timestamptz or %text. +They can also be addressed using their fully qualified schema names: +%pg_catalog.int4, %pg_catalog.timestamptz, %pg_catalog.text or +%myschema.mytype to avoid ambiguity. + +.SS Specifier Strings +\fISpecifier Mark\fP +.br +A specifier mark indicates where a specifier begins. A mark is not part +of the type name. + + % Marks the beginning of a type name. When getting results, this + also indicates that a field will be referenced by number. + + # Marks the beginning of a type name. When getting results, this + also indicates that a field will be referenced by name. + + @ Marks the beginning of a prepared specifier name, see + PQspecPrepare(). When used, it must be the first and only + specifier in the format string: "@prepared_spec", "@myfunc", + etc... NOTE: the \'@\' must be the first character, no spaces. + +\fIType Specifier\fP +.br +Type specifiers are comprised of an optional schema name and type name. +Type specifiers have a set of rules: + + -) Format: [schema].type - optional schema name, a "." separator + between schema and type and the type name. + + -) First character must be a-z or an underscore + + -) Double quotes are required for characters not in [a-zA-Z0-9_] + NOTE: In libpqtypes, this includes "double precision" + + -) Schema "." separator, specifier marks or flags are not included + in double quotes + + -) Non-quoted type names are casefolded, quoted names are not. + + -) Examples: + "%int4" + "%pg_catalog.int4" + "%\\"my oDd~ !tYpe naMe#\\"" + "%myschema.\\"my oDd~ !tYpe naMe#\\"" + "%abc.int4 %pg_catalog.int4" <= fully qualified type names + + -) Last in First out: To find a type referenced in a specifier + string, the search begins with the last type registered. + User registered type handlers are searched first, followed + by builtin types. + + -) pqt schema: There is a builtin schema named pqt. By default, + it contains two types: \'str\' and \'null\'. Anything can be + put into this schema, which has nothing to do with the server. + This is good for aliases or type sub-classes that are + client-specific. + +\fISpecifier Flag\fP +.br +Flags are used to alter the behavior of a type specifier. They are always placed +at the end of the specifier name. If the name is double quoted, the flag is just +after the closing quote. + + * This is called the pass-by-pointer flag. It is supported by all + builtin types during a \fIPQputf(3)\fP. For integer, bool, money and + "char" types (scalars), one can omit this flag to pass-by-value. For + all other types, omitting this flag has no effect; they are always + passed by pointer and thus this flag is not needed for things like + text, timestamp, uuid, etc.. + + Putting data: this flag tells libpqtypes to store a direct + pointer to the data being put, rather than making a copy of + it. For scalars (ints, bool, etc..), this tell libpqtypes + to dereference the pointer to get the value. However, an upside + is that you can pass in a C NULL for the scalar pointer and it + will produce an SQL NULL when put. + + Getting data: no built-in types make use of the pointer flag. + User-defined type handlers can make the pointer flag behave + anyway they see fit. The \'get\' type handler is supplied a + PGtypeArgs which contains an \'is_ptr\' member. + + [] This is called the array flag. It indicates that an array is + being referenced rather than a simple type. This flag is always + used with a PGarray. +.SH NUMERICS +.LP +With the exception of the "numeric" type, all numeric types behave +identically: int2, int4, int8, float4 and float8. +.RS +.nf +.LP +PG type C type +PGint2 short +PGint4 int +PGint8 long long (platform dependent) +PGfloat4 float +PGfloat8 double +.fi +.RE + +Putting numeric values: If the value supplied is too large for the PG +type, it will be silently truncated. Note the use of the pass-by-pointer +flag being used for the last two arguments, allowing you to easily put +an SQL NULL for any numeric type. +.RS +.nf +\fB +PGint2 i2 = 10; +PQputf(param, "%int2 %int4 %int8 %float4 %float8 %int2* %int8*", + SHRT_MAX, INT_MAX, LLONG_MAX, 1234.56, 123456.789, &i2, NULL);\fP +.fi +.RE + +Getting numeric values: Like scanf, the correctly sized data type must be +used. For instance: you cannot use a 4-byte int for %int2 - you must use a short. +.RS +.nf +\fB +// Read an int2 from field 0, int4 from field 1, int8 from +// field 2, float4 from field 3 and a float8 from field 4 +PGint2 i2; +PGint4 i4; +PGint8 i8; +PGfloat4 f4; +PGfloat8 f8; +PQgetf(result, tup_num, "%int2 %int4 %int8 %float4 %float8", + 0, &i2, 1, &i4, 2, &i8, 3, &f4, 4, &f8);\fP +.fi +.RE + +The numeric data type is always exposed in text format. +There is no C data structure. It is always sent in binary format. +.RS +.nf +\fB +PQputf(param, "%numeric", "1728718718271827121233.121212121212");\fP +.fi +.RE + +Even if binary results are used when getting a numeric value, libpqtypes will +internally convert the numeric to text. This has the advantage of allowing +you to use binary results and still have access to numeric fields. If you +want to work with a numeric in binary form, use PQgetvalue() on a binary result set. +.RS +.nf +\fB +PGnumeric numstr; +PQgetf(result, tup_num, "%numeric", field_num, &numstr);\fP +.fi +.RE + +The first argument is the field number of the numeric. The second argument is a +pointer to a PGnumeric to receive the numeric string value (which will +always be NUL terminated). +.SH ARRAY +.LP +Arrays are put using the PGarray structure. Elements are put using \fIPQputf(3)\fP +into a PGparam structure contained withn a PGarray. The PGarray contains +array dimension members for specifing the number of dimension, the +dimensions of each dimension and the lower bounds of each dimension. + +Arrays are only handled using binary format. This means that any type used +as an array element must be put and gotten in binary format. If a +user-defined type does not implement a send and recv function in the backend, +it can not be used as an array element. + +For a discussion of composite arrays, \`man pqt-composites(3)\'. +.RS +.nf +\fB +typedef struct +{ + /* The number of array dimensions. Specifing zero for this + * value on puts has special meaning. When zero, this value + * is set to one, dims[0] is set to the number of items in + * the \'param\' member and lbound[0] is set to one. + */ + int ndims; + + /* An array of lower bounds for each dimension. */ + int lbound[MAXDIM]; + + /* An array of dimensions for each dimension. */ + int dims[MAXDIM]; + + /* When putting array elements, this PGparam is used. Each + * item put is one array element. Because the \fIPQputf(3)\fP + * interface allows putting more than one item at a time, you + * can put multiple array elements. + */ + PGparam *param; + + /* When getting an array, this is the PGresult object that + * contains the array elements. Each element is one tuple, + * regardless of the array dimensions. If the array has 100 + * elements across 3 dimensions, PQntuples(arr.res) will return + * 100. The only valid field, for non-composite arrays, is + * field zero. + */ + PGresult *res; +} PGarray; +\fP +.fi +.RE + +When all elements have been put, the PGarray structure must be put using the +"[]" array specifer flag into a PGparam structure. \fIPQputf(3)\fP is used to build the +array elements and to put the resulting PGarray. + +Putting an array value: +.RS +.nf +\fB +PGint4 i; +PGarray arr; +PGparam *param; + +/* One dimensional arrays do not require setting dimension info. For + * convience, you can zero the structure or set ndims to zero. + */ +arr.ndims = 0; + +/* create the param object that will contain the elements */ +arr.param = PQparamCreate(conn); + +/* Use \fIPQputf(3)\fP to put the array elements */ +for(i=0; i < 1000; i++) + PQputf(arr.param, "%int4", i); + +/* The PGarray must be put into a PGparam struture. So far, only + * the array elements have been put. \'param\' can continue to be + * used to pack more parameters. The array is now a single parameter + * within \'param\'. + */ +param = PQparamCreate(conn); +PQputf(param, "%int[]", &arr); + +/* no longer needed */ +PQparamClear(arr.param); +\fP +.fi +.RE + +To get an array, \fIPQgetf(3)\fP is used in conjunction with the PGarray +structure. The result object contained with the PGarray is populated +with the array elements. The dimension info is assigned as well. Each +array element is its own tuple that only contains a single field for non +composite arrays. + +Getting an array value: +.RS +.nf +\fB +int i; +PGint4 val; +int ntups; +PGarray arr; +PGresult *result = ...; + +PQgetf(result, 0, "%int[]", 0, &arr); + +/* not needed anymore*/ +PQclear(result); + +/* each tuple is an array element */ +ntups = PQntuples(arr.res); +for(i=0; i < ntups; i++) +{ + /* Always field 0 */ + PQgetf(arr.res, i, "%int4", 0, &val); + printf("[%03d] %d\\n", i, val); +} + +PQclear(arr.res); +\fP +.fi +.RE + +The result object is not organized based on the dimension info. Indexes are always +zero-based. If the dimension info is meaningful to your application, index +translation must be done using the ndims, dims and lbound members of the PGarray +structure. + +You cannot reference an array element by field name. The only logical name for an +array element would be the string version of its index ... "0", "1", etc.. The +index value of a non-composite array is its tuple number, the field number is always +zero. This means "#int" is not legal for non-composite arrays. You must use +"%int" and reference field 0. +.SH CHAR +.LP +The "char" data type uses the PGchar. The value is limited to 8-bits. + +Putting a "char" value: +.RS +.nf +\fB +PGchar c = \'a\'; +PQputf(param, "%char %char %char*", 213, c, NULL);\fP +.fi +.RE + +Getting a "char" value: +.RS +.nf +\fB +PGchar c; +PQgetf(result, tup_num, "%char", field_num, &c);\fP +.fi +.RE +.SH VARIABLE-LENGTH CHARACTER TYPES +.LP +BPCHAR, VARCHAR, NAME and TEXT are handled identically. libpqtypes does no range +checking on NAME, BPCHAR or VARCHAR, it lets the server perform these checks. +There are two ways to put strings: allow libpqtypes to make an internal copy of the +string (default behavior) or as a direct pointer: (both require that the C +string is NUL-terminated) +.RS +.nf +\fB +/* Put a string so libpqtypes makes a copy. In this case, + * 4 copies would be made of the same string. + */ +PGtext str = "foobar"; +PQputf(param, "%bpchar %varchar %name %text", str, str, str, str); + +/* Put a string so libpqtypes doesn\'t make a copy, + * keeps a direct pointer. More efficient than above, + * especially if these are large strings. + */ +PQputf(param, "%bpchar* %varchar* %name* %text*", str, str, str, str);\fP +.fi +.RE + +WARNING: Be careful about variable scope when using the "*" specifier flag: +.RS +.nf +\fB +/* when \'func\' returns, the str pointer becomes invalid! + * The below should be using "%text" ... w/o the * flag. + */ +int func(PGparam *param) +{ + PGchar str[16]; + strcpy(str, "foobar"); + return PQputf(param, "%text*", str); // BAD IDEA! +}\fP +.fi +.RE + +To \fIPQgetf(3)\fP a string, you supply a pointer to a PGtext. Unlike putting string +values, getting them doesn\'t make use of the "*" specifier flag (silently ignored). +.RS +.nf +\fB +/* Get a string value */ +PGvarchar str; +PQgetf(result, tup_num, "%varchar", field_num, &str); + +/* identical to */ +str = PQgetvalue(result, tup_num, field_num);\fP +.fi +.RE + +The reason the \'*\' specifier flag is silently ignored, rather than raising a syntax +error, is it is common to define macros for specifer strings; that can be used for +puts and gets: +.RS +.nf +\fB +/* user_id, username, password */ +#define TYPESPEC_USERINFO "%int4 %text* %text*" + +PGint4 uid = 0; +PGtext user = "foo", pass = "bar"; +PQputf(param, TYPESPEC_USERINFO, uid, user, pass); +PQgetf(param, tup_num, TYPESPEC_USERINFO, 0, &uid, 1, &user, 2, &pass);\fP +.fi +.RE + +The above allowance is more useful than a syntax error. +.SH BYTEA +.LP +There are two ways to put a bytea: copy or direct pointer (just like variable-length +character types). In either case, you supply a pointer to a PGbytea. +.RS +.nf +\fB +typedef struct +{ + int len; /* number of bytes */ + char *data; /* pointer to the bytea data */ +} PGbytea; + +/* Put a bytea letting libpqtypes make a copy; */ +PGbytea bytea = {4, {0, 1, 2, 3}}; +PQputf(param, "%bytea", &bytea); + +/* Put a bytea not letting libpqtypes make a copy, stores a + * direct pointer to PGbytea.data. + */ +PQputf(param, "%bytea*", &bytea);\fP +.fi +.RE + +To get a bytea, you provide a pointer to a PGbytea. Unlike putting bytea +values, there is only one way to get them. +.RS +.nf +\fB +/* Get a bytea value (exposed as binary, no + * escaping/unescaping needed) + */ +PGbytea bytea; +PQgetf(result, tup_num, "%bytea", field_num, &bytea);\fP +.fi +.RE + +NOTE: For text results, PQgetlength will not match the length returned by \fIPQgetf(3)\fP. +The values \fIPQgetf(3)\fP assigns to the user provided PGbytea* represent the unescaped bytea value. +.SH DATE +.LP +PGdate is used by DATE, TIMESTAMP and TIMESTAMPTZ data types. To put a date, +you must set the isbc, year, mon and mday members. All other members are ignored. + +.RS +.nf +\fBtypedef struct +{ + /* When non-zero, the date is in the BC ERA. */ + int isbc; + + /* + * The BC or AD year, which is NOT adjusted by 1900 like + * the POSIX struct tm. Years are always positive values, + * even BC years. To distinguish between BC and AD years, + * use the isbc flag: (year 0 not used) + * Ex. -1210 is represented as: isbc=1, year=1209 + */ + int year; + + /* The number of months since January, in the range 0 to 11. */ + int mon; + + /* The day of the month, in the range 1 to 31. */ + int mday; + + /* The Julian day in the Gregorian calendar. */ + int jday; + + /* The number of days since January 1, in the range 0 to 365. */ + int yday; + + /* The number of days since Sunday, in the range 0 to 6. */ + int wday; +} PGdate;\fP +.fi +.RE + +Putting a date value: +.RS +.nf +\fB +// \'1401-01-19 BC\' +PGdate date; +date.isbc = 1; +date.year = 1401; +date.mon = 0; +date.mday = 19; +PQputf(param, "%date", &date);\fP +.fi +.RE + +Getting a date value: +.RS +.nf +\fB +PQgetf(result, tup_num, "%date", field_num, &date);\fP +.fi +.RE +.SH TIME +.LP +PGtime is used by TIME, TIMETZ, TIMESTAMP and TIMESTAMPTZ data types. To put a time, you must set the hour, min, sec and usec members. All other members are ignored. +.RS +.nf +\fB +typedef struct +{ + /* The number of hours past midnight, in the range 0 to 23. */ + int hour; + + /* The number of minutes after the hour, in the + * range 0 to 59. + */ + int min; + + /* The number of seconds after the minute, in the + * range 0 to 59. + */ + int sec; + + /* The number of microseconds after the second, in the + * range of 0 to 999999. + */ + int usec; + + /* + * When non-zero, this is a TIME WITH TIME ZONE. Otherwise, + * it is a TIME WITHOUT TIME ZONE. + */ + int withtz; + + /* A value of 1 indicates daylight savings time. A value of 0 + * indicates standard time. A value of -1 means unknown or + * could not determine. + */ + int isdst; + + /* Offset from UTC in seconds. This value is not always + * available. It is set to 0 if it cannot be determined. + */ + int gmtoff; + + /* Timezone abbreviation: such as EST, GMT, PDT, etc. + * This value is not always available. It is set to an empty + * string if it cannot be determined. + */ + char tzabbr[16]; +} PGtime; +\fP +.fi +.RE + +Putting a time value: +.RS +.nf +\fB +// \'10:41:06.002897\' +PGdate time; +time.hour = 10; +time.min = 41; +time.sec = 6; +time.usec = 2897; +PQputf(param, "%time", &time);\fP +.fi +.RE + +Getting a time value: +.RS +.nf +\fB +PQgetf(result, tup_num, "%time", field_num, &time); +\fP +.fi +.RE +.SH TIMETZ +.LP +The TIMETZ data type uses the PGtime structure, for a description of this +structure see the TIME section. To put a timetz, you must set the hour, min, +sec, usec and gmtoff members. All other members are ignored. + +Putting a timetz value: +.RS +.nf +\fB +// \'10:41:06.002897-05\' +PGdate timetz; +timetz.hour = 10; +timetz.min = 41; +timetz.sec = 6; +timetz.usec = 2897; +timetz.gmtoff = -18000; +PQputf(param, "%timetz", &timetz); +\fP +.fi +.RE + +Getting a timetz value: +.RS +.nf +\fB +PQgetf(result, tup_num, "%timetz", field_num, &timetz); +\fP +.fi +.RE +.SH TIMESTAMP +.LP +To put a timestamp, the isbc, year, mon, mday, hour, min, sec and usec members must be set. No other members are used. +.RS +.nf +\fB +typedef struct +{ + /* The number seconds before or after midnight UTC of + * January 1, 1970, not counting leap seconds. + */ + PGint8 epoch; + + /* The date part of the timestamp. */ + PGdate date; + + /* The time part of the timestamp. */ + PGtime time; +} PGtimestamp; +\fP +.fi +.RE + +Putting a timestamp value: +.RS +.nf +\fB +// \'2000-01-19 10:41:06\' +PGtimestamp ts; +ts.date.isbc = 0; +ts.date.year = 2000; +ts.date.mon = 0; +ts.date.mday = 19; +ts.time.hour = 10; +ts.time.min = 41; +ts.time.sec = 6; +ts.time.usec = 0; +PQputf(param, "%timestamp", &ts); +\fP +.fi +.RE + +Getting a timestamp value: +.RS +.nf +\fB +PQgetf(result, tup_num, "%timestamp", field_num, &ts); +\fP +.fi +.RE + +The timestamp type has no concept of timezone, so the value returned by \fIPQgetf(3)\fP is +exactly what the server sent; no timezone adjustments are attempted. The gmtoff is always +set to zero, tzabbr will be an empty string and withtz will be zero. +.SH TIMESTAMPTZ +.LP +To put a timestamptz, the isbc, year, mon, mday, hour, min, sec, usec and gmtoff +members must be set. No other members are used. + +Putting a timestamptz value: +.RS +.nf +\fB +// \'2000-01-19 10:41:06-05\' +PGtimestamp ts; +ts.date.isbc = 0; +ts.date.year = 2000; +ts.date.mon = 0; +ts.date.mday = 19; +ts.time.hour = 10; +ts.time.min = 41; +ts.time.sec = 6; +ts.time.usec = 0; +ts.time.gmtoff = -18000; +PQputf(param, "%timestamptz", &ts); +\fP +.fi +.RE + +Getting a timestamptz value: +.RS +.nf +\fB +PQgetf(result, tup_num, "%timestamptz", field_num, &ts); +\fP +.fi +.RE + +When using \fIPQgetf(3)\fP in binary mode, the timestamptz value is converted into the local +machine\'s timezone. If the local machine's timezone can not be determined, the +value will be in GMT (gmtoff is set to zero and tzabbr is set to GMT). + +When using \fIPQgetf(3)\fP in text mode, the timestamptz value is returned as a datetime string +in the server\'s timezone. No adjustments are made to this value. If the server is +using a DateStyle that encodes the gmtoff "00:00:00-05", then gmtoff will be set to +this value and tzabbr will be "GMT+/-hhmmss" (00:00:00-05 => GMT-0500). In this case, +isdst is set to -1 ... meaning unknown. If the server\'s DateStyle encodes a timezone +abbreviation, like PST, then tzabbr is set to this value. The gmtoff and isdst members +are properly set: + +.nf + DateStyle includes a timezone abbrev - "SQL, MDY" + 01/25/2007 00:00:00 EST => tzabbr=EST, gmtoff=-18000, isdst=0 + 01/25/2007 01:00:00 EDT => tzabbr=EDT, gmtoff=-14400, isdst=1 +.fi +.SH INTERVAL +.LP +To put an interval, all relevant members of a PGinterval should be assigned and those +not used should be set to zero. +.RS +.nf +\fB +typedef struct +{ + /* the number of years */ + int years; + + /* the number of months */ + int mons; + + /* the number of days */ + int days; + + /* the number of hours */ + int hours; + + /* the number of mins */ + int mins; + + /* the number of seconds */ + int secs; + + /* the number of microseconds */ + int usecs; +} PGinterval; +\fP +.fi +.RE + +Putting an interval value: +.RS +.nf +\fB +// "20 years 8 months 9 hours 10 mins 15 secs 123456 usecs" +PGinterval interval; +interval.years = 20; +interval.mons = 8; +interval.days = 0; // not used, set to 0 +interval.hours = 9; +interval.mins = 10; +interval.secs = 15; +interval.usecs = 123456; +PQputf(param, "%interval", &interval); +\fP +.fi +.RE + +Getting an interval value: +.RS +.nf +\fB +PQgetf(result, tup_num, "%interval", field_num, &interval); +\fP +.fi +.RE + +NOTE: When using text results with a non-ISO DateStyle, microseconds are +truncated to a 2 digit value. For example: "4 mins 2.11 secs" but microseconds +is really 111456. +.SH POINT +.LP +The PGpoint structure is used to put and get a point. +.RS +.nf +\fB +typedef struct +{ + double x; // point x value + double y; // point y value +} PGpoint; +\fP +.fi +.RE + +Putting a point value: +.RS +.nf +\fB +PGpoint pt = {12.345, 6.789}; +PQputf(param, "%point", &pt); +\fP +.fi +.RE + +Getting a point value: +.RS +.nf +\fB +PGpoint pt; +PQgetf(result, tup_num, "%point", field_num, &pt); +\fP +.fi +.RE +.SH LSEG +.LP +The PGlseg structure is used to put and get a line segnment. +.RS +.nf +\fB +typedef struct +{ + PGpoint pts[2]; +} PGlseg; +\fP +.fi +.RE + +Putting a lseg value: +.RS +.nf +\fB +PGlseg lseg = {{{12.345, 6.789}, {99.8, 88.9}}}; +PQputf(param, "%lseg", &lseg); +\fP +.fi +.RE + +Getting a lseg value: +.RS +.nf +\fB +PGlseg lseg; +PQgetf(result, tup_num, "%lseg", field_num, &lseg); +\fP +.fi +.RE +.SH BOX +.LP +The PGbox structure is used to put and get a box. +.RS +.nf +\fB +typedef struct +{ + PGpoint high; + PGpoint low; +} PGbox; +\fP +.fi +.RE + +Putting a box value: +.RS +.nf +\fB +PGbox box = {{12.345, 6.789}, {22.234, 1.9998}}; +PQputf(param, "%box", &box); +\fP +.fi +.RE + +Getting a box value: +.RS +.nf +\fB +PGbox box; +PQgetf(result, tup_num, "%box", field_num, &box); +\fP +.fi +.RE +.SH CIRCLE +.LP +The PGcircle structure is used to put and get a circle. +.RS +.nf +\fB +typedef struct +{ + PGpoint center; + double radius; +} PGcircle; +\fP +.fi +.RE + +Putting a circle value: +.RS +.nf +\fB +PGcircle circle = {{12.345, 6.789}, 2.34567}; +PQputf(param, "%circle", &circle); +\fP +.fi +.RE + +Getting a circle value: +.RS +.nf +\fB +PGcircle circle; +PQgetf(result, tup_num, "%circle", field_num, &circle); +\fP +.fi +.RE +.SH PATH +.LP +The PGpath structure is used to put and get a path. If the closed member is +non-zero, the path is closed, otherwise it is open. +.RS +.nf +\fB +typedef struct +{ + int npts; + int closed; + PGpoint *pts; +} PGpath; +\fP +.fi +.RE + +Putting a path value: +.RS +.nf +\fB +// Put a closed path that contains 2 points +PGpoint pts[] = {{12.345, 6.789}, {19.773, 7.882}}; +PGpath path = {2, 1, pts}; +PQputf(param, "%path", &path); +\fP +.fi +.RE + +Getting a path value: +.RS +.nf +\fB +PGpath path; +if(PQgetf(result, tup_num, "%path", field_num, &path)) +{ + // path.pts must be copied out if needed after clearing results + copy_points(path.npts, path.pts, ...); + + PQclear(result); + // path.pts is now invalid! +} +\fP +.fi +.RE +.SH POLYGON +.LP +The PGpolygon structure is used to put and get a polygon. +.RS +.nf +\fB +typedef struct +{ + int npts; + PGpoint *pts; +} PGpolygon; +\fP +.fi +.RE + +Putting a polygon value: +.RS +.nf +\fB +// Put a polygon that contains 2 points +PGpoint pts[] = {{12.345, 6.789}, {19.773, 7.882}}; +PGpolygon polygon = {2, 1, pts}; +PQputf(param, "%polygon", &polygon); +\fP +.fi +.RE + +Getting a polygon value: +.RS +.nf +\fB +PGpolygon polygon; +if(PQgetf(result, tup_num, "%polygon", field_num, &polygon)) +{ + // polygon.pts must be copied out if needed after clearing results + copy_points(polygon.npts, polygon.pts, ...); + + PQclear(result); + // polygon.pts is now invalid +} +\fP +.fi +.RE +.SH INET & CIDR +.LP +When putting an inet or cidr, all members must be set excluding the sa_len. +.RS +.nf +\fB +typedef struct +{ + /* The address mask, 32 for a single IP. */ + int mask; + + /* When non-zero, the PGinet structure represents a cidr + * otherwise an inet. + */ + int is_cidr; + + /* the length in bytes of the sa_buf member. */ + int sa_len; + + /* the socket address buffer, contains the data. This can + * be casted to a sockaddr, sockaddr_in, sockaddr_in6 or a + * sockaddr_storage structure. This buffer is 128 bytes so + * that it is large enough for a sockaddr_storage structure. + */ + char sa_buf[128]; +} PGinet; +\fP +.fi +.RE + +Putting an inet or cidr: +.RS +.nf +\fB +socklen_t len; +PGinet inet; + +cli_fd = accept(srv_fd, (struct sockaddr *)inet.sa_buf, &len); +if(cli_fd != -1) +{ + inet.is_cidr = 0; + inet.mask = 32; + PQputf(param, "%inet", &inet); +} +\fP +.fi +.RE + +Getting an inet or cidr: +.RS +.nf +\fB +PGinet inet; +unsigned short port; + +/* gets an inet from field 2 and an int2 from field 6 */ +if(PQgetf(result, tup_num, "%inet %int2", 2, &inet, 6, &port)) +{ + char ip[80]; + struct sockaddr *sa = (struct sockaddr *)inet.sa_buf; + + // converting a PGinet to an IPv4 or IPv6 address string + getnameinfo(sa, inet.sa_len, ip, sizeof(ip), + NULL, 0, NI_NUMERICHOST); + + // The inet data type does not store a port. + if(sa->sa_family == AF_INET) + ((struct sockaddr_in *)sa)->sin_port = htons(port); + else + ((struct sockaddr_in6 *)sa)->sin6_port = htons(port); + + printf("Connecting to %s:%d\\n", ip, port); + connect(sock_fd, sa, inet.sa_len); +} +\fP +.fi +.RE +.SH MACADDR +.LP +The PGmacaddr structure is used to put and get a macaddr. +.RS +.nf +\fB +typedef struct +{ + int a; + int b; + int c; + int d; + int e; + int f; +} PGmacaddr; +\fP +.fi +.RE + +Putting a macaddr value: +.RS +.nf +\fB +PGmacaddr macaddr = {0, 1, 2, 3, 4, 5}; +PQputf(param, "%macaddr", &macaddr); +\fP +.fi +.RE + +Getting a macaddr value: +.RS +.nf +\fB +PGmacaddr macaddr; +PQgetf(result, tup_num, "%macaddr", field_num, &macaddr); +\fP +.fi +.RE +.SH MONEY +.LP +The money type is put/get as a PGmoney (64-bit integer). It can be +converted to dollar and cents format by dividing by 100: +double money = (double)money64 / 100.0;. Pre 8.3 servers are limited to +32-bit money values. + +Putting a money value (put two money values, one is an SQL NULL): +.RS +.nf +\fB +PGmoney money = 600000000054LL; // 6 billion dollars and 54 cents +PQputf(param, "%money %money*", money, NULL); +\fP +.fi +.RE + +Getting a money value: +.RS +.nf +\fB +PQgetf(result, tup_num, "%money", field_num, &money); +\fP +.fi +.RE +.SH BOOL +.LP +The bool type is put/get as a PGbool. To put true or false, use 1 or 0. + +Putting a bool value (this puts true and a bool SQL NULL): +.RS +.nf +\fB +PGbool b = 1; // put true +PQputf(param, "%bool %bool*", b, NULL); +\fP +.fi +.RE + +Getting a bool value: +.RS +.nf +\fB +PGbool b; +PQgetf(result, tup_num, "%bool", field_num, &b); +\fP +.fi +.RE +.SH UUID +.LP +The uuid type is put/get as a sequence of 16 bytes. To put a uuid as +text, use "%str". NOTE: this type is not available on pre 8.3 servers. + +Putting a uuid value: +.RS +.nf +\fB +PGuuid uuid = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; +PQputf(param, "%uuid", uuid); +\fP +.fi +.RE + +Getting a uuid value: +.RS +.nf +\fB +PGuuid uuid; +PQgetf(result, tup_num, "%uuid", field_num, &uuid); +\fP +.fi +.RE + +WARNING: The data provided on a put call is expected to be at least 16 bytes. +.SH OID +.LP +Putting an oid value: +.RS +.nf +\fB +Oid oid = 2318; +PQputf(param, "%oid", oid); +\fP +.fi +.RE + +Getting an oid value: +.RS +.nf +\fB +Oid oid; +PQgetf(result, tup_num, "%oid", field_num, &oid); +\fP +.fi +.RE +.SH EXAMPLES +.LP +None. +.SH AUTHOR +.LP +A contribution of eSilo, LLC. for the PostgreSQL Database Management System. +Written by Andrew Chernow and Merlin Moncure. +.SH REPORTING BUGS +.LP +Report bugs to . +.SH COPYRIGHT +.LP +Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +.br +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +.LP +\fIPQgetf(3)\fP, \fIPQputf(3)\fP. diff --git a/source/groff2html b/source/groff2html new file mode 100644 index 0000000..c8a0182 --- /dev/null +++ b/source/groff2html @@ -0,0 +1,109 @@ +#!/bin/bash + +######################################################################## +# Converts a man page to very simple html, basically a page with +# A and PRE tags. Takes 1 arguments: path to the non-compressed man +# page. This expects the man page to be named PAGE_NAME.SECTION_NUMBER; +# like `PQgetf.3' or `printf.1'. +# +# FEATURES +# 1. Any some_man(3), no spaces before '(', is substituted for an +# A tag linking to http://libpqtypes.esilo.com/man3/$man.html +# +# 2. All '<' and '>' are replaced with HTML codes +# +# 3. Mans referencing other mans, via .so macro, are converted +# to symlinks in the output directory. +# +# ISSUES +# Hyperlinks are not 100% substitued into resulting html page. +# The only time there are issues is when the link text in the +# man page was broken across lines. No work-around at this time +# for this issue other than manually ensuring line breaks don't +# occur on `some_man(3)'. +# +# Man pages that reference other man pages, using the .so macro, +# must have the .so command in the first 'head' lines. The +# .so 'path_to_man' cannot include spaces. +###################################################################### + +DOCTYPE="" + +# adjust these to your web server +BASEURL="http:\/\/libpqtypes.esilo.com" +INC_BASEURL="$BASEURL\/include" +MAN_BASEURL="$BASEURL\/man" +OUTDIR=/esilo/www/libpqtypes.esilo.com/man +HEAD_SECTION="" +HEADER="libpqtypes home page

" +FOOTER="

libpqtypes home page" + +if [ $# = 0 ] ; then + echo "Must supply man page to convert" + exit +fi + +# ignore non-existent files +if test ! -f $1 ; then + exit +fi + +manpage=`basename $1` +section=`echo "$manpage" | awk -F . '{print $NF}'` +manpage=${manpage%.[^.]*} + +OUTDIR="${OUTDIR}${section}" +mkdir -p $OUTDIR + +# If a .so reference, create a symlink +solink=`head $1 | grep '.so .*' | cut -d ' ' -f 2` +if test ! -z $solink ; then + target=`basename $solink` + target=${target%.[^.]*} + echo "$OUTDIR/$manpage.html => $OUTDIR/$target.html" + + # remove existing symlink + rm -f $OUTDIR/$manpage.html + ln -s $OUTDIR/$target.html $OUTDIR/$manpage.html + exit +fi + +echo $OUTDIR/$manpage.html + +>$OUTDIR/$manpage.html + +# doctype +if test ! -z "$DOCTYPE" ; then + echo $DOCTYPE >>$OUTDIR/$manpage.html +fi + +# add stadard html tags, include a title +echo -e "\n\n$HEAD_SECTION\nman $manpage\n\n" >>$OUTDIR/$manpage.html + +if test ! -z "$HEADER" ; then + echo $HEADER >>$OUTDIR/$manpage.html +fi + +echo "

" >>$OUTDIR/$manpage.html
+
+# 1. output man in ascii
+# 2. post-process with col removing backspaces and tabs
+# 3. do some html code replacement 
+# 4. replace some_man(3) with A tags
+groff -t -e -mandoc -Tascii $1 2>/dev/null | col -bx | \
+sed 's//\>/g' | \
+sed "s/\<\([a-ZA-Z0-9_\-]*\.h\)\>/\<\1<\/a>\>/g" | \
+sed "s/\b\([a-zA-Z0-9_\-]\+\)(\([0-9]\))/\1(\2)<\/a>/g" \
+>>$OUTDIR/$manpage.html
+
+echo -e "
\n" >>$OUTDIR/$manpage.html + +if test ! -z "$FOOTER" ; then + echo $FOOTER >>$OUTDIR/$manpage.html +fi + +# html closing tags +echo -e "\n" >>$OUTDIR/$manpage.html + +chown apache:apache $OUTDIR/$manpage.html + diff --git a/source/latest.diff b/source/latest.diff new file mode 100644 index 0000000..70597cd --- /dev/null +++ b/source/latest.diff @@ -0,0 +1,709 @@ +Index: ChangeLog +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/ChangeLog,v +retrieving revision 1.64 +retrieving revision 1.67 +diff -r1.64 -r1.67 +10a11,36 +> (NOT RELEASED), v1.5.2 +> - BUG: Fixed putting binary empty strings. Previously, the string was +> incorrectly converted to SQL NULLs, but now are properly packed and +> transmitted. +> - Reported by: Dmitry Epstein +> - FEATURE: Added ability to put SQL NULLs for int2, int4, int8, float4, float8, +> numeric,"char", bool and money types. This is accomplished by using +> the type-spec pointer flag "*" and allowing these types to be passed as +> pointers: +> >> +> int *i = NULL; +> if(something_is_true) +> *i = 10; +> PQputf(param, "%int4*", i); +> >> +> Before this change, the use of "*" flag for these types was ignored +> and passing any of them as pointers was not allowed. +> - Requested by: Dmitry Epstein and several on stackoverflow. +> http://stackoverflow.com/questions/18684331/passing-sql-null-as-a-sql-statement-parameter-value-using-the-postgresql-libpqty +> - Updated Copyright statement from 2011 to 2008 - 2015, affected all source +> code and man page files. +> - Update an example within docs/man3/PQputf.3 to include putting a NULL +> using the pass-by-pointer type-specifier flag '*'. +> - Updated docs/man3/pqt-specs.3 to include documentation in regard to putting +> NULLs using the pass-by-pointer flag. +> +19c45 +< TLS key for each thread's error structure. For whatever reason, +--- +> TLS key for each thread's error structure. For whatever reason, +Index: LICENSE +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/LICENSE,v +retrieving revision 1.3 +retrieving revision 1.4 +diff -r1.3 -r1.4 +5c5 +< Copyright (c) 2009 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +20c20 +< AND ESILO, LLC. HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +--- +> AND ESILO, LLC. HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +Index: docs/man3/PQexecf.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQexecf.3,v +retrieving revision 1.6 +retrieving revision 1.8 +diff -r1.6 -r1.8 +1c1 +< .TH "PQexecf" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQexecf" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +96c96 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQgeterror.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQgeterror.3,v +retrieving revision 1.8 +retrieving revision 1.10 +diff -r1.8 -r1.10 +1c1 +< .TH "PQgeterror" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQgeterror" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +49c49 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQgetf.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQgetf.3,v +retrieving revision 1.9 +retrieving revision 1.11 +diff -r1.9 -r1.11 +1c1 +< .TH "PQgetf" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQgetf" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +97c97 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQinitTypes.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQinitTypes.3,v +retrieving revision 1.1 +retrieving revision 1.3 +diff -r1.1 -r1.3 +1c1 +< .TH PQinitTypes 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH PQinitTypes 3 2008-2015 "libpqtypes" "libpqtypes Manual" +47c47 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQlocalTZInfo.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQlocalTZInfo.3,v +retrieving revision 1.5 +retrieving revision 1.7 +diff -r1.5 -r1.7 +1c1 +< .TH "PQlocalTZInfo" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQlocalTZInfo" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +59c59 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQparamCreate.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamCreate.3,v +retrieving revision 1.9 +retrieving revision 1.11 +diff -r1.9 -r1.11 +1c1 +< .TH "PQparamCreate" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQparamCreate" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +69c69 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQparamExec.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamExec.3,v +retrieving revision 1.6 +retrieving revision 1.8 +diff -r1.6 -r1.8 +1c1 +< .TH "PQparamExec" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQparamExec" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +99c99 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQparamSendQuery.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamSendQuery.3,v +retrieving revision 1.4 +retrieving revision 1.6 +diff -r1.4 -r1.6 +1c1 +< .TH "PQparamSendQuery" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQparamSendQuery" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +82c82 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQputf.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQputf.3,v +retrieving revision 1.9 +retrieving revision 1.11 +diff -r1.9 -r1.11 +1c1 +< .TH "PQputf" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQputf" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +47c47,49 +< The example uses \fIPQputf\fP() to put a couple parameters into a PGparam. +--- +> The example uses \fIPQputf\fP() to put a few parameters into a PGparam. Note +> that the last parameter puts an SQL NULL as an int4 using the pass-by-pointer +> type-specifier pointer flag '*', rather than pass-by-value. +52a55 +> PGint4 *i4ptr = NULL; +55c58 +< if(!PQputf(param, "%text %int8", text, i8)) +--- +> if(!PQputf(param, "%text %int8 %int4*", text, i8, i4ptr)) +110c113 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQregisterComposites.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQregisterComposites.3,v +retrieving revision 1.3 +retrieving revision 1.5 +diff -r1.3 -r1.5 +1c1 +< .TH "PQregisterComposites" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQregisterComposites" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +72c72 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQregisterResult.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQregisterResult.3,v +retrieving revision 1.1 +retrieving revision 1.3 +diff -r1.1 -r1.3 +1c1 +< .TH "PQregisterResult" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQregisterResult" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +112c112 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQregisterSubClasses.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQregisterSubClasses.3,v +retrieving revision 1.2 +retrieving revision 1.4 +diff -r1.2 -r1.4 +1c1 +< .TH "PQregisterSubClasses" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQregisterSubClasses" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +54c54 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQregisterTypes.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQregisterTypes.3,v +retrieving revision 1.2 +retrieving revision 1.4 +diff -r1.2 -r1.4 +1c1 +< .TH "PQregisterTypes" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQregisterTypes" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +81c81 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQregisterUserDefinedTypes.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQregisterUserDefinedTypes.3,v +retrieving revision 1.3 +retrieving revision 1.5 +diff -r1.3 -r1.5 +1c1 +< .TH "PQregisterUserDefinedTypes" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQregisterUserDefinedTypes" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +74c74 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQspecPrepare.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQspecPrepare.3,v +retrieving revision 1.13 +retrieving revision 1.15 +diff -r1.13 -r1.15 +1c1 +< .TH "PQspecPrepare" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "PQspecPrepare" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +115c115 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/PQtypesRegister.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQtypesRegister.3,v +retrieving revision 1.5 +retrieving revision 1.7 +diff -r1.5 -r1.7 +1c1 +< .TH PQtypesRegister 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH PQtypesRegister 3 2008-2015 "libpqtypes" "libpqtypes Manual" +45c45 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/pqt-composites.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/pqt-composites.3,v +retrieving revision 1.8 +retrieving revision 1.10 +diff -r1.8 -r1.10 +1c1 +< .TH "pqt-composites" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "pqt-composites" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +256c256 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/pqt-handlers.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/pqt-handlers.3,v +retrieving revision 1.12 +retrieving revision 1.14 +diff -r1.12 -r1.14 +1c1 +< .TH "pqt-handlers" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "pqt-handlers" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +155c155 +< /* The memory behind the 'attDescs' pointer when the number of +--- +> /* The memory behind the 'attDescs' pointer when the number of +591c591 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: docs/man3/pqt-specs.3 +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/pqt-specs.3,v +retrieving revision 1.14 +retrieving revision 1.16 +diff -r1.14 -r1.16 +1c1 +< .TH "pqt-specs" 3 2011 "libpqtypes" "libpqtypes Manual" +--- +> .TH "pqt-specs" 3 2008-2015 "libpqtypes" "libpqtypes Manual" +78,81c78,83 +< * This is called the pointer flag. It is only supported on a +< handful of builtin types during a \fIPQputf(3)\fP, but user registered +< types can provide support for them. Supported types are: +< VARCHAR, BPCHAR, TEXT, BYTEA, NAME and the pqt.str. +--- +> * This is called the pass-by-pointer flag. It is supported by all +> builtin types during a \fIPQputf(3)\fP. For integer, bool, money and +> "char" types (scalars), one can omit this flag to pass-by-value. For +> all other types, omitting this flag has no effect; they are always +> passed by pointer and thus this flag is not needed for things like +> text, timestamp, uuid, etc.. +85c87,90 +< it. +--- +> it. For scalars (ints, bool, etc..), this tell libpqtypes +> to dereference the pointer to get the value. However, an upside +> is that you can pass in a C NULL for the scalar pointer and it +> will produce an SQL NULL when put. +112c117,119 +< type, it will be silently truncated. +--- +> type, it will be silently truncated. Note the use of the pass-by-pointer +> flag being used for the last two arguments, allowing you to easily put +> an SQL NULL for any numeric type. +116,117c123,125 +< PQputf(param, "%int2 %int4 %int8 %float4 %float8", +< SHRT_MAX, INT_MAX, LLONG_MAX, 1234.56, 123456.789);\fP +--- +> PGint2 i2 = 10; +> PQputf(param, "%int2 %int4 %int8 %float4 %float8 %int2* %int8*", +> SHRT_MAX, INT_MAX, LLONG_MAX, 1234.56, 123456.789, &i2, NULL);\fP +305c313 +< PQputf(param, "%char %char", 213, c);\fP +--- +> PQputf(param, "%char %char %char*", 213, c, NULL);\fP +1106c1114 +< Putting a money value: +--- +> Putting a money value (put two money values, one is an SQL NULL): +1111c1119 +< PQputf(param, "%money", money); +--- +> PQputf(param, "%money %money*", money, NULL); +1128c1136 +< Putting a bool value: +--- +> Putting a bool value (this puts true and a bool SQL NULL): +1133c1141 +< PQputf(param, "%bool", b); +--- +> PQputf(param, "%bool %bool*", b, NULL); +1206c1214 +< Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/array.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/array.c,v +retrieving revision 1.11 +retrieving revision 1.13 +diff -r1.11 -r1.13 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/datetime.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/datetime.c,v +retrieving revision 1.12 +retrieving revision 1.14 +diff -r1.12 -r1.14 +7c7 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/error.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/error.c,v +retrieving revision 1.24 +retrieving revision 1.26 +diff -r1.24 -r1.26 +10c10 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/events.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/events.c,v +retrieving revision 1.16 +retrieving revision 1.18 +diff -r1.16 -r1.18 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/exec.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/exec.c,v +retrieving revision 1.25 +retrieving revision 1.27 +diff -r1.25 -r1.27 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/geo.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/geo.c,v +retrieving revision 1.11 +retrieving revision 1.13 +diff -r1.11 -r1.13 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/handler.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/handler.c,v +retrieving revision 1.29 +retrieving revision 1.31 +diff -r1.29 -r1.31 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/libpqtypes-int.h +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/libpqtypes-int.h,v +retrieving revision 1.23 +retrieving revision 1.25 +diff -r1.23 -r1.25 +7c7 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/libpqtypes.h +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/libpqtypes.h,v +retrieving revision 1.35 +retrieving revision 1.37 +diff -r1.35 -r1.37 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/misc.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/misc.c,v +retrieving revision 1.10 +retrieving revision 1.13 +diff -r1.10 -r1.13 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +17c17,30 +< *args->put.out = (PGchar) va_arg(args->ap, int); +--- +> PGchar c; +> +> if (args->is_ptr) +> { +> PGchar *ptr = (PGchar *) va_arg(args->ap, PGchar *); +> PUTNULLCHK(args, ptr); +> c = *ptr; +> } +> else +> { +> c = (PGchar) va_arg(args->ap, int); +> } +> +> *args->put.out = c; +34c47,60 +< *args->put.out = ((char) va_arg(args->ap, PGbool)!=0) ? 1 : 0; +--- +> char b; +> +> if (args->is_ptr) +> { +> PGbool *ptr = (PGbool *) va_arg(args->ap, PGbool *); +> PUTNULLCHK(args, ptr); +> b = (char) *ptr; +> } +> else +> { +> b = (char) va_arg(args->ap, PGbool); +> } +> +> *args->put.out = b != 0 ? 1 : 0; +56c82 +< PGmoney money = va_arg(args->ap, PGmoney); +--- +> PGmoney money; +58a85,95 +> if (args->is_ptr) +> { +> PGmoney *ptr = va_arg(args->ap, PGmoney *); +> PUTNULLCHK(args, ptr); +> money = *ptr; +> } +> else +> { +> money = va_arg(args->ap, PGmoney); +> } +> +Index: src/network.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/network.c,v +retrieving revision 1.10 +retrieving revision 1.12 +diff -r1.10 -r1.12 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/numerics.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/numerics.c,v +retrieving revision 1.11 +retrieving revision 1.14 +diff -r1.11 -r1.14 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +70c70,82 +< PGint2 n = (PGint2) va_arg(args->ap, int); +--- +> PGint2 n; +> +> if (args->is_ptr) +> { +> PGint4 *nptr = va_arg(args->ap, PGint4 *); +> PUTNULLCHK(args, nptr); +> n = (PGint2) *nptr; +> } +> else +> { +> n = (PGint2) va_arg(args->ap, PGint4); +> } +> +103c115,127 +< PGint4 n = va_arg(args->ap, PGint4); +--- +> PGint4 n; +> +> if (args->is_ptr) +> { +> PGint4 *nptr = va_arg(args->ap, PGint4 *); +> PUTNULLCHK(args, nptr); +> n = *nptr; +> } +> else +> { +> n = (PGint4) va_arg(args->ap, PGint4); +> } +> +136c160,172 +< PGint8 i8 = va_arg(args->ap, PGint8); +--- +> PGint8 i8; +> +> if (args->is_ptr) +> { +> PGint8 *nptr = va_arg(args->ap, PGint8 *); +> PUTNULLCHK(args, nptr); +> i8 = *nptr; +> } +> else +> { +> i8 = va_arg(args->ap, PGint8); +> } +> +163,164c199,211 +< PGfloat4 f = (PGfloat4) va_arg(args->ap, double); +< void *vp = (void *)&f; +--- +> void *vp; +> +> if (args->is_ptr) +> { +> vp = (void *) va_arg(args->ap, double *); +> PUTNULLCHK(args, vp); +> } +> else +> { +> PGfloat4 f = (PGfloat4) va_arg(args->ap, double); +> vp = (void *)&f; +> } +> +196c243,255 +< PGfloat8 d = va_arg(args->ap, PGfloat8); +--- +> PGfloat8 d; +> +> if (args->is_ptr) +> { +> PGfloat8 *ptr = (PGfloat8 *) va_arg(args->ap, PGfloat8 *); +> PUTNULLCHK(args, ptr); +> d = *ptr; +> } +> else +> { +> d = va_arg(args->ap, PGfloat8); +> } +> +234a294,295 +> PUTNULLCHK(args, str); +> +Index: src/param.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/param.c,v +retrieving revision 1.23 +retrieving revision 1.27 +diff -r1.23 -r1.27 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +415a416,424 +> else if (datal == 0) +> { +> /* BUG FIX: allows putting empty strings in binary mode. Without this, +> * empty strings would be converted to an SQL NULL. +> * Reported by: Dmitry Epstein +> */ +> static char zero[1] = {0}; +> v->data = zero; +> } +Index: src/port.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/port.c,v +retrieving revision 1.8 +retrieving revision 1.10 +diff -r1.8 -r1.10 +7c7 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/record.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/record.c,v +retrieving revision 1.9 +retrieving revision 1.11 +diff -r1.9 -r1.11 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/spec.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/spec.c,v +retrieving revision 1.10 +retrieving revision 1.12 +diff -r1.10 -r1.12 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/utils.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/utils.c,v +retrieving revision 1.11 +retrieving revision 1.13 +diff -r1.11 -r1.13 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. +Index: src/varlena.c +=================================================================== +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/varlena.c,v +retrieving revision 1.6 +retrieving revision 1.8 +diff -r1.6 -r1.8 +6c6 +< * Copyright (c) 2011 eSilo, LLC. All rights reserved. +--- +> * Copyright (c) 2008-2015 eSilo, LLC. All rights reserved. diff --git a/source/latest.log b/source/latest.log new file mode 100644 index 0000000..3f51890 --- /dev/null +++ b/source/latest.log @@ -0,0 +1,1167 @@ +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/AUTHORS,v +Working file: AUTHORS +head: 1.4 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 4; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/ChangeLog,v +Working file: ChangeLog +head: 1.67 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 67; selected revisions: 3 +description: +---------------------------- +revision 1.67 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +9 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +---------------------------- +revision 1.66 +date: 2015/01/27 12:25:53; author: achernow; state: Exp; lines: +7 -4 +updated changelog and bug fix comment to give credit to those that reported the issues +---------------------------- +revision 1.65 +date: 2015/01/26 17:23:04; author: achernow; state: Exp; lines: +17 -1 +fixed empty string bug and putting NULLs for numberics, bools, etc.. +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/INSTALL,v +Working file: INSTALL +head: 1.6 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 6; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/LICENSE,v +Working file: LICENSE +head: 1.4 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 4; selected revisions: 1 +description: +---------------------------- +revision 1.4 +date: 2015/01/27 16:34:49; author: achernow; state: Exp; lines: +2 -2 +updated copyright date within LICENSE file, which was missed by previous refactor since it had a different date. +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/Makefile.am,v +Working file: Makefile.am +head: 1.24 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 24; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/Makefile.win32,v +Working file: Makefile.win32 +head: 1.7 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 7; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/README,v +Working file: README +head: 1.6 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 6; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/acinclude.m4,v +Working file: acinclude.m4 +head: 1.2 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 2; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/configure.ac,v +Working file: configure.ac +head: 1.29 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 29; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/groff2html,v +Working file: groff2html +head: 1.12 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 12; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/reconf,v +Working file: reconf +head: 1.3 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 3; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/win32.mak,v +Working file: win32.mak +head: 1.5 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 5; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQclearSpecs.3,v +Working file: docs/man3/PQclearSpecs.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQclearTypes.3,v +Working file: docs/man3/PQclearTypes.3 +head: 1.2 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 2; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQexecf.3,v +Working file: docs/man3/PQexecf.3 +head: 1.8 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 8; selected revisions: 2 +description: +---------------------------- +revision 1.8 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.7 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQexecvf.3,v +Working file: docs/man3/PQexecvf.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQgetErrorField.3,v +Working file: docs/man3/PQgetErrorField.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQgeterror.3,v +Working file: docs/man3/PQgeterror.3 +head: 1.10 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 10; selected revisions: 2 +description: +---------------------------- +revision 1.10 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.9 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQgetf.3,v +Working file: docs/man3/PQgetf.3 +head: 1.11 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 11; selected revisions: 2 +description: +---------------------------- +revision 1.11 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.10 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQgetvf.3,v +Working file: docs/man3/PQgetvf.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQinitTypes.3,v +Working file: docs/man3/PQinitTypes.3 +head: 1.3 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 3; selected revisions: 2 +description: +---------------------------- +revision 1.3 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.2 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQlocalTZInfo.3,v +Working file: docs/man3/PQlocalTZInfo.3 +head: 1.7 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 7; selected revisions: 2 +description: +---------------------------- +revision 1.7 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.6 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamClear.3,v +Working file: docs/man3/PQparamClear.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamCount.3,v +Working file: docs/man3/PQparamCount.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamCreate.3,v +Working file: docs/man3/PQparamCreate.3 +head: 1.11 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 11; selected revisions: 2 +description: +---------------------------- +revision 1.11 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.10 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamDup.3,v +Working file: docs/man3/PQparamDup.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamExec.3,v +Working file: docs/man3/PQparamExec.3 +head: 1.8 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 8; selected revisions: 2 +description: +---------------------------- +revision 1.8 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.7 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamExecPrepared.3,v +Working file: docs/man3/PQparamExecPrepared.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamReset.3,v +Working file: docs/man3/PQparamReset.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamSendQuery.3,v +Working file: docs/man3/PQparamSendQuery.3 +head: 1.6 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 6; selected revisions: 2 +description: +---------------------------- +revision 1.6 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.5 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQparamSendQueryPrepared.3,v +Working file: docs/man3/PQparamSendQueryPrepared.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQputf.3,v +Working file: docs/man3/PQputf.3 +head: 1.11 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 11; selected revisions: 2 +description: +---------------------------- +revision 1.11 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.10 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +7 -4 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQputvf.3,v +Working file: docs/man3/PQputvf.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQregisterComposites.3,v +Working file: docs/man3/PQregisterComposites.3 +head: 1.5 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 5; selected revisions: 2 +description: +---------------------------- +revision 1.5 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.4 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQregisterResult.3,v +Working file: docs/man3/PQregisterResult.3 +head: 1.3 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 3; selected revisions: 2 +description: +---------------------------- +revision 1.3 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.2 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/Attic/PQregisterSubClass.3,v +Working file: docs/man3/PQregisterSubClass.3 +head: 1.3 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 3; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQregisterSubClasses.3,v +Working file: docs/man3/PQregisterSubClasses.3 +head: 1.4 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 4; selected revisions: 2 +description: +---------------------------- +revision 1.4 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.3 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/Attic/PQregisterTypeHandler.3,v +Working file: docs/man3/PQregisterTypeHandler.3 +head: 1.9 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 9; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQregisterTypes.3,v +Working file: docs/man3/PQregisterTypes.3 +head: 1.4 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 4; selected revisions: 2 +description: +---------------------------- +revision 1.4 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.3 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQregisterUserDefinedTypes.3,v +Working file: docs/man3/PQregisterUserDefinedTypes.3 +head: 1.5 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 5; selected revisions: 2 +description: +---------------------------- +revision 1.5 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.4 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQsendf.3,v +Working file: docs/man3/PQsendf.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQsendvf.3,v +Working file: docs/man3/PQsendvf.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQseterror.3,v +Working file: docs/man3/PQseterror.3 +head: 1.1 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 1; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQspecPrepare.3,v +Working file: docs/man3/PQspecPrepare.3 +head: 1.15 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 15; selected revisions: 2 +description: +---------------------------- +revision 1.15 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.14 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/Attic/PQtypesObjectHooks.3,v +Working file: docs/man3/PQtypesObjectHooks.3 +head: 1.5 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 5; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/PQtypesRegister.3,v +Working file: docs/man3/PQtypesRegister.3 +head: 1.7 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 7; selected revisions: 2 +description: +---------------------------- +revision 1.7 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.6 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/pqt-composites.3,v +Working file: docs/man3/pqt-composites.3 +head: 1.10 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 10; selected revisions: 2 +description: +---------------------------- +revision 1.10 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.9 +date: 2015/01/27 16:27:36; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/pqt-handlers.3,v +Working file: docs/man3/pqt-handlers.3 +head: 1.14 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 14; selected revisions: 2 +description: +---------------------------- +revision 1.14 +date: 2015/01/27 16:32:21; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.13 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +3 -3 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/docs/man3/pqt-specs.3,v +Working file: docs/man3/pqt-specs.3 +head: 1.16 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 16; selected revisions: 2 +description: +---------------------------- +revision 1.16 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.15 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +23 -15 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/array.c,v +Working file: src/array.c +head: 1.13 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 13; selected revisions: 2 +description: +---------------------------- +revision 1.13 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.12 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/datetime.c,v +Working file: src/datetime.c +head: 1.14 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 14; selected revisions: 2 +description: +---------------------------- +revision 1.14 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.13 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/error.c,v +Working file: src/error.c +head: 1.26 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 26; selected revisions: 2 +description: +---------------------------- +revision 1.26 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.25 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/events.c,v +Working file: src/events.c +head: 1.18 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 18; selected revisions: 2 +description: +---------------------------- +revision 1.18 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.17 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/exec.c,v +Working file: src/exec.c +head: 1.27 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 27; selected revisions: 2 +description: +---------------------------- +revision 1.27 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.26 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/geo.c,v +Working file: src/geo.c +head: 1.13 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 13; selected revisions: 2 +description: +---------------------------- +revision 1.13 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.12 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/getaddrinfo.h,v +Working file: src/getaddrinfo.h +head: 1.4 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 4; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/handler.c,v +Working file: src/handler.c +head: 1.31 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 31; selected revisions: 2 +description: +---------------------------- +revision 1.31 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.30 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/Attic/hooks.c,v +Working file: src/hooks.c +head: 1.5 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 5; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/libpqtypes-int.h,v +Working file: src/libpqtypes-int.h +head: 1.25 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 25; selected revisions: 2 +description: +---------------------------- +revision 1.25 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.24 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/libpqtypes.h,v +Working file: src/libpqtypes.h +head: 1.37 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 37; selected revisions: 2 +description: +---------------------------- +revision 1.37 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.36 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/misc.c,v +Working file: src/misc.c +head: 1.13 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 13; selected revisions: 3 +description: +---------------------------- +revision 1.13 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.12 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +---------------------------- +revision 1.11 +date: 2015/01/26 17:23:04; author: achernow; state: Exp; lines: +40 -3 +fixed empty string bug and putting NULLs for numberics, bools, etc.. +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/network.c,v +Working file: src/network.c +head: 1.12 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 12; selected revisions: 2 +description: +---------------------------- +revision 1.12 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.11 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/numerics.c,v +Working file: src/numerics.c +head: 1.14 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 14; selected revisions: 3 +description: +---------------------------- +revision 1.14 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.13 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +2 -2 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +---------------------------- +revision 1.12 +date: 2015/01/26 17:23:04; author: achernow; state: Exp; lines: +67 -6 +fixed empty string bug and putting NULLs for numberics, bools, etc.. +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/param.c,v +Working file: src/param.c +head: 1.27 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 27; selected revisions: 4 +description: +---------------------------- +revision 1.27 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.26 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +---------------------------- +revision 1.25 +date: 2015/01/27 12:25:53; author: achernow; state: Exp; lines: +1 -0 +updated changelog and bug fix comment to give credit to those that reported the issues +---------------------------- +revision 1.24 +date: 2015/01/26 17:23:04; author: achernow; state: Exp; lines: +8 -0 +fixed empty string bug and putting NULLs for numberics, bools, etc.. +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/port.c,v +Working file: src/port.c +head: 1.10 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 10; selected revisions: 2 +description: +---------------------------- +revision 1.10 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.9 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/record.c,v +Working file: src/record.c +head: 1.11 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 11; selected revisions: 2 +description: +---------------------------- +revision 1.11 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.10 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/regression-test.c,v +Working file: src/regression-test.c +head: 1.28 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 28; selected revisions: 0 +description: +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/spec.c,v +Working file: src/spec.c +head: 1.12 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 12; selected revisions: 2 +description: +---------------------------- +revision 1.12 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.11 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/utils.c,v +Working file: src/utils.c +head: 1.13 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 13; selected revisions: 2 +description: +---------------------------- +revision 1.13 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.12 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= + +RCS file: /var/lib/gforge/chroot/cvsroot/libpqtypes/libpqtypes/src/varlena.c,v +Working file: src/varlena.c +head: 1.8 +branch: +locks: strict +access list: +symbolic names: +keyword substitution: kv +total revisions: 8; selected revisions: 2 +description: +---------------------------- +revision 1.8 +date: 2015/01/27 16:32:22; author: achernow; state: Exp; lines: +1 -1 +mis-tyyped copyright date change (extra spaces), had to refactor affecting all source and man3 files +---------------------------- +revision 1.7 +date: 2015/01/27 16:27:37; author: achernow; state: Exp; lines: +1 -1 +update pqt-specs.3, PQputf.3 and copyright notices in all source and man3 files +============================================================================= diff --git a/source/reconf b/source/reconf new file mode 100644 index 0000000..7b76083 --- /dev/null +++ b/source/reconf @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# http://opendx.npaci.edu/mail/opendx-dev/2003.07/msg00081.html + +rm -f aclocal.m4 + +CAT=false + +#You should add the contents of `/usr/local/share/aclocal/libtool15.m4' to `aclocal.m4'. +OUT=$(libtoolize --force --copy | grep "You should add the contents of \`[^']*' to \`aclocal.m4'.") +if [ $? == 0 ] ; then + ACLOCAL=$(echo $OUT | sed "s/You should add the contents of \`//;s/' to \`aclocal.m4'.//") + if [ $? == 0 ] ; then + CAT=true + fi +fi + +aclocal +if [ "$CAT" == "true" ] ; then + cat $ACLOCAL >> aclocal.m4 +fi +autoheader +automake -a -i +autoconf + diff --git a/source/src/array.c b/source/src/array.c new file mode 100644 index 0000000..c3354c1 --- /dev/null +++ b/source/src/array.c @@ -0,0 +1,278 @@ + +/* + * array.c + * Type handler for the array data type. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +int +pqt_put_array(PGtypeArgs *args) +{ + int i; + int hasnull=0; + int ndims; + int nitems; + int arrsize; + char *out; + int lbound[MAXDIM]; + int dims[MAXDIM]; + PGarray *arr = va_arg(args->ap, PGarray *); + + PUTNULLCHK(args, arr); + + if (arr->ndims < 0) + return args->errorf(args, "arr.ndims is invalid - %d", arr->ndims); + + /* auto configure when ndims is 0 to 1d array */ + if (arr->ndims == 0) + { + ndims = 1; + dims[0] = arr->param->vcnt; + lbound[0] = 1; + } + else + { + ndims = arr->ndims; + memcpy(lbound, arr->lbound, sizeof(lbound)); + memcpy(dims, arr->dims, sizeof(dims)); + } + + nitems = 1; + for (i=0; i < ndims; i++) + nitems *= dims[i]; + + /* make sure array is on the same page as the param */ + if (nitems != arr->param->vcnt) + return args->errorf(args, + "param element count %d is different than array's %d", + arr->param->vcnt, nitems); + + /* header: ndims + hasnull + elemtype + ((dims + lbound) * ndims) */ + arrsize = 4 + 4 + 4 + (8 * ndims); + + /* compute data length, also get the hasnull flag */ + for (i=0; i < arr->param->vcnt; i++) + { + if (arr->param->vals[i].format == 0) + return args->errorf(args, "Cannot put array elements in text format"); + + arrsize += 4; + if (arr->param->vals[i].datal == NULL_LEN) + hasnull = 1; + else + arrsize += arr->param->vals[i].datal; + } + + /* make sure args->put.out is large enough */ + if (args->put.expandBuffer(args, arrsize) == -1) + return -1; + + out = args->put.out; + + /* number od dimensions */ + pqt_buf_putint4(out, ndims); + out += 4; + + /* array hasnull flag */ + pqt_buf_putint4(out, hasnull); + out += 4; + + /* array element oid */ + pqt_buf_putint4(out, args->typhandler->typoid); + out += 4; + + /* dims and lbound */ + for (i=0; i < ndims; i++) + { + pqt_buf_putint4(out, dims[i]); + out += 4; + + pqt_buf_putint4(out, lbound[i]); + out += 4; + } + + /* write the element lengths and data */ + for (i=0; i < arr->param->vcnt; i++) + { + pqt_buf_putint4(out, arr->param->vals[i].datal); + out += 4; + + if (arr->param->vals[i].datal > 0) + { + memcpy(out, arr->param->vals[i].data, arr->param->vals[i].datal); + out += arr->param->vals[i].datal; + } + } + + return arrsize; +} + +int +pqt_get_array(PGtypeArgs *args) +{ + int i,t; + int vlen; + int ntups; + int nattrs; + Oid elemoid; + DECLVALUE(args); + PGresult *res; + int first_tup; + PGarray *arr = va_arg(args->ap, PGarray *); + + CHKGETVALS(args, arr); + + if (args->format == TEXTFMT) + return args->errorf(args, "array does not support text results"); + + /* number of dims */ + arr->ndims = pqt_buf_getint4(value); + value += 4; + + /* skip NULL flag */ + value += 4; + + /* check the element oid */ + elemoid = (Oid)pqt_buf_getint4(value); + if (elemoid != args->typhandler->typoid) + return args->errorf(args, + "array element type %u is different than what server says %u", + args->typhandler->typoid, elemoid); + value += 4; + + /* arr dims and lbounds */ + first_tup = 1; + for (i=0, ntups=1; i < arr->ndims; i++) + { + arr->dims[i] = pqt_buf_getint4(value); + value += 4; + + arr->lbound[i] = pqt_buf_getint4(value); + value += 4; + + ntups *= arr->dims[i]; + } + + /* This means ndims is zero because the above loop never iterated. */ + if (i == 0) + ntups = 0; + + /* numTuples is the number of array items + * and numAttributes is 1 for non-composites. + */ + nattrs = (args->typhandler->nattrs > 0) ? args->typhandler->nattrs : 1; + + if (!(res = pqt_copyresult(args, nattrs))) + RERR_MEM(args); + + for (t=0; t < ntups; t++) + { + /* get the value len */ + vlen = pqt_buf_getint4(value); + value += 4; + + /* Regular Array with 1 attr per tuple */ + if (args->typhandler->nattrs == 0) + { + /* set the field value */ + if (!PQsetvalue(res, t, 0, value, vlen)) + { + PQclear(res); + return -1; + } + + if (vlen > 0) + value += vlen; + + continue; + } + + /* ------------------------ + * COMPOSITE/RECORD + */ + + /* NULL compsoite array item, fill in attrs with NULL */ + if (vlen == NULL_LEN) + { + int x; + + for (x=0; x < nattrs; x++) + { + if (!PQsetvalue(res, t, x, NULL, NULL_LEN)) + { + PQclear(res); + return -1; + } + } + + /* move on to next tuple, done here. */ + continue; + } + + /* verify that server's attr count matches ours */ + if (first_tup) + { + int attcnt = pqt_buf_getint4(value); + + /* watch for invalidation issues */ + if (attcnt != nattrs) + { + PQclear(res); + return args->errorf(args, + "type handler attribute count is %d but server says it's %d", + args->typhandler->nattrs, attcnt); + } + } + + /* skip attr count */ + value += 4; + + /* composite attributes (record columns) */ + for (i=0; i < nattrs; i++) + { + /* watch for invalidation issues */ + if (first_tup && + (Oid) pqt_buf_getint4(value) != args->typhandler->attDescs[i].attoid) + { + Oid server_oid = (Oid) pqt_buf_getint4(value); + + args->errorf(args, + "type handler attribute OID is %u but server says it's %u", + args->typhandler->attDescs[i].attoid, server_oid); + + PQclear(res); + return -1; + } + + /* skip oid */ + value += 4; + + /* get the value length */ + vlen = pqt_buf_getint4(value); + value += 4; + + /* set the field value */ + if (!PQsetvalue(res, t, i, value, vlen)) + { + PQclear(res); + return -1; + } + + if (vlen > 0) + value += vlen; + } + + first_tup = 0; + } + + arr->res = res; + return 0; +} + + diff --git a/source/src/datetime.c b/source/src/datetime.c new file mode 100644 index 0000000..54d8228 --- /dev/null +++ b/source/src/datetime.c @@ -0,0 +1,1707 @@ + +/* + * datetime.c + * Type handler for TIME, TIMETZ, DATE, TIMESTAMP and TIMESTAMPTZ + * data types. Also includes public function PQlocalTZInfo. + * + * Copyright (c) 2008-2015 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. + */ + +/* Microsoft went overboard with these warnings. This avoids + * warnings about _timezone and _tzname. The alternatives + * provided by microsoft are terrible. All other warnings, + * like strdup, snprintf, strcpy, etc.. have been avoided + * by using Microsoft alternate functions. This whole thing + * is completely moronic. + */ +#define _CRT_SECURE_NO_WARNINGS + +#include "libpqtypes-int.h" + +#if defined(PQT_WIN32) || defined(HAVE_TIME_H) +# include +#endif + +#if defined(HAVE_CONFIG_H) && defined(HAVE_SYS_TIME_H) +# include +#endif + +#define MINGMTOFF -53940 /* -1459 */ +#define MAXGMTOFF 53940 /* +1459 */ +#define ISVALIDGMTOFF(off) ((off) >= MINGMTOFF && (off) <= MAXGMTOFF) + +#define CHECKDATEVALS(_args, _d) do{ \ + if ((_d)->year < 0) \ + RERR(_args, "invalid year value ... cannot be negative"); \ + if ((_d)->mon < 0 || (_d)->mon > 11) \ + RERR(_args, "invalid month value ... range is 0 to 11"); \ + if ((_d)->mday < 1 || (_d)->mday > 31) \ + RERR(_args, "invalid day value ... range is 1 to 31"); \ +}while (0) + +#define CHECKTIMEVALS(_args, _t, _withtz) do{ \ + if ((_t)->hour < 0 || (_t)->hour > 23) \ + RERR(_args, "invalid hour value ... range is 0 to 23"); \ + if ((_t)->min < 0 || (_t)->min > 59) \ + RERR(_args, "invalid minute value ... range is 0 to 59"); \ + if ((_t)->sec < 0 || (_t)->sec > 59) \ + RERR(_args, "invalid second value ... range is 0 to 59"); \ + if ((_t)->usec < 0 || (_t)->usec > 999999) \ + RERR(_args, "invalid microsecond value ... range is 0 to 999999"); \ + if ((_withtz) && !ISVALIDGMTOFF((_t)->gmtoff)) \ + return (_args)->errorf(_args, \ + "invalid gmtoff value ... range is %d to %d", MINGMTOFF, MAXGMTOFF); \ +}while (0) + +#define MONTHS_PER_YEAR 12 +#define SECS_PER_YEAR (36525 * 864) /* avoid floating-point computation */ +#define SECS_PER_DAY 86400 +#define SECS_PER_HOUR 3600 +#define SECS_PER_MINUTE 60 +#define MINS_PER_HOUR 60 +#define USECS_PER_DAY PQT_INT64CONST(86400000000) +#define USECS_PER_HOUR PQT_INT64CONST(3600000000) +#define USECS_PER_MINUTE PQT_INT64CONST(60000000) +#define USECS_PER_SEC PQT_INT64CONST(1000000) +#define UNIX_EPOCH_JDATE 2440588 /* == date2j(1970, 1, 1) */ +#define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */ + +#define TS_PREC_INV 1000000.0 +#define TSROUND(j) (rint(((double) (j)) * TS_PREC_INV) / TS_PREC_INV) + +/* round off to MAX_TIME_PRECISION decimal places */ +#define TIME_PREC_INV 10000000000.0 +#define TIMEROUND(j) (rint(((double) (j)) * TIME_PREC_INV) / TIME_PREC_INV) + +#define TMODULO(t,q,u) \ +do { \ + (q) = (int) (((t) < 0) ? ceil((t) / (u)) : floor((t) / (u))); \ + if ((q) != 0) (t) -= rint((q) * (u)); \ +} while (0) + +/* Converts "1209 BC" notation to "-1208" */ +#define BC2YEAR(_isbc, _yr) (_isbc) ? -((_yr) - 1) : (_yr) +/* Converts "-1208" to "1209 BC" notation */ +#define YEAR2BC(_yr) ((_yr) <= 0) ? -((_yr) - 1) : (_yr) + +/* date & time functions */ +static int date2j(int y, int m, int d); +static void gmtoff2name(int gmtoff, char *tzNameBuf, size_t size); +static void tzabbr2info(const char *tzabbr, int *gmtoff, int *isdst); +static int text2time(char *timestr, PGtime *time, int withtz); +static int text2interval(char *intvlstr, PGinterval *interval); +static int text2date(const char *datestr, PGdate *date, + const char *datestyle); +static int bin2pgdate(int d, PGdate *date); +static int bin2pgtime(double t, int gmtoff, PGtime *time, int withtz); +static int bin2pginterval(double tval, int days, int mons, + PGinterval *interval); +static int bin2pgts(double ts, PGtimestamp *pgts, int withtz); +static char *time2t(PGtypeArgs *args, PGtime *t, void *val); + +#ifdef PQT_WINAPI +static char *_win32_tzabbr(const char *fullname); +#endif + +#if defined(HAVE_CONFIG_H) && !defined(HAVE_FLOOR) +static double +floor(double x) +{ + /* also works if modf always returns a positive fractional part */ + double val; + return modf(x, &val) < 0 ? val - 1.0 : val ; +} +#endif + +#if defined(HAVE_CONFIG_H) && !defined(HAVE_CEIL) +static double +ceil(double x) +{ + /* also works if modf always returns a positive fractional part */ + double val; + return modf(x, &val) > 0 ? val + 1.0 : val ; +} +#endif + +/* MSVC and unixes w/o rint */ +#if defined(PQT_MSVC) || (defined(HAVE_CONFIG_H) && !defined(HAVE_RINT)) +static double +rint(double x) +{ + double f, n = 0.; + + f = modf(x, &n); + + if (x > 0.) + { + if (f > .5) + n += 1.; + } + else if (x < 0.) + { + if (f < -.5) + n -= 1.; + } + + return n; +} +#endif + +/* + * time: PGtime members must be set: hour, min, sec, usec. + * timetz: PGtime members must be set: hour, min, sec, usec, gmtoff. + */ +static int +put_time2(PGtypeArgs *args, int withtz) +{ + int r=8; + char tbuf[8]; + PGtime *time = va_arg(args->ap, PGtime *); + + PUTNULLCHK(args, time); + CHECKTIMEVALS(args, time, withtz); + + pqt_swap8(args->put.out, time2t(args, time, tbuf), 1); + + if (withtz) + { + pqt_buf_putint4(args->put.out + 8, -time->gmtoff); + r += 4; + } + + return r; +} + +int +pqt_put_time(PGtypeArgs *args) +{ + return put_time2(args, 0); +} + +int +pqt_put_timetz(PGtypeArgs *args) +{ + return put_time2(args, 1); +} + +/* Also handles timetz */ +static int +get_time2(PGtypeArgs *args, int withtz) +{ + DECLVALUE(args); + double t; + int gmtoff=0; + char tbuf[8]; + PGtime *time = va_arg(args->ap, PGtime *); + + CHKGETVALS(args, time); + + if (args->format == TEXTFMT) + { + if (text2time(value, time, withtz) == -1) + RERR(args, "invalid time format"); + return 0; + } + + /* read and convert binary data */ + pqt_swap8(tbuf, value, 0); + if (withtz) + gmtoff = -pqt_buf_getint4(value + 8); + + /* Convert 8 byte integer format to double precision. */ + if (args->fmtinfo->integer_datetimes) + { + PGint8 n = *(PGint8 *) tbuf; + t = (double) n / 1000000.0; + } + else + { + t = *(double *) tbuf; + } + + /* convert binary data to a PGtime */ + if (bin2pgtime(t, gmtoff, time, withtz) == -1) + RERR(args, "negative julian day detected"); + + return 0; +} + +int +pqt_get_time(PGtypeArgs *args) +{ + return get_time2(args, 0); +} + +int +pqt_get_timetz(PGtypeArgs *args) +{ + return get_time2(args, 1); +} + +/* PGdate members required isbc, year, mon, mday */ +int +pqt_put_date(PGtypeArgs *args) +{ + int dval; + PGdate *date = va_arg(args->ap, PGdate *); + + PUTNULLCHK(args, date); + CHECKDATEVALS(args, date); + + dval = date2j(BC2YEAR(date->isbc, date->year), date->mon+1, date->mday) + - POSTGRES_EPOCH_JDATE; + + pqt_buf_putint4(args->put.out, dval); + return 4; +} + +int +pqt_get_date(PGtypeArgs *args) +{ + DECLVALUE(args); + int d; + PGdate *date = va_arg(args->ap, PGdate *); + + CHKGETVALS(args, date); + + if (args->format == TEXTFMT) + { + if (text2date(value, date, args->fmtinfo->datestyle) == -1) + RERR(args, "invalid date format"); + return 0; + } + + d = pqt_buf_getint4(value); + if (bin2pgdate(d, date) == -1) + RERR(args, "binary date conversion failed"); + + return 0; +} + +/* + * timestamp: The timestamp is never converted from what is provided. The + * timezone info is dropped as pgtype 'time' has no concept of timezones. + * The following PGtimestamp members must be set: date.isbc, date.year, + * date.mon, date.mday, time.hour, time.min, time.sec, time.usec. + * + * timestamptz: The timestamptz is always converted to GMT. Its adjusted + * by PGtime.gmtoff, so set this value to zero if you already have GMT. + * The following PGtimestamp members must be set: date.isbc, date.year, + * date.mon, date.mday, time.hour, time.min, time.sec, time.usec, + * time.gmtoff. + */ +static int +put_timestamp2(PGtypeArgs *args, int withtz) +{ + char tbuf[8]; + int year, mon; + PGtimestamp *pgts = va_arg(args->ap, PGtimestamp *); + PGtime *t = &pgts->time; + + PUTNULLCHK(args, pgts); + CHECKDATEVALS(args, &pgts->date); + CHECKTIMEVALS(args, t, withtz); + + mon = pgts->date.mon + 1; + year = BC2YEAR(pgts->date.isbc, pgts->date.year); + + time2t(args, t, tbuf); + + if (args->fmtinfo->integer_datetimes) + { + PGint8 time = *(PGint8 *) tbuf; + int date = date2j(year, mon, pgts->date.mday) - POSTGRES_EPOCH_JDATE; + PGint8 val = (PGint8) (date * USECS_PER_DAY + time); + + /* check for major overflow */ + if ((val - time) / USECS_PER_DAY != date) + RERR(args, "timestamp overflow"); + + /* check for just-barely overflow (okay except time-of-day wraps) */ + if ((val < 0 && date >= 0) || (val >= 0 && date < 0)) + RERR(args, "timestamp overflow"); + + /* currently in localtime, convert to GMT */ + if (withtz) + val -= (t->gmtoff * USECS_PER_SEC); + + pqt_swap8(args->put.out, &val, 1); + } + else + { + double time = *(double *) tbuf; + double date = date2j(year, mon, pgts->date.mday) - POSTGRES_EPOCH_JDATE; + double val = (double) (date * SECS_PER_DAY + time); + + /* currently in localtime, convert to GMT */ + if (withtz) + val -= (t->gmtoff); + + pqt_swap8(args->put.out, &val, 1); + } + + return 8; +} + +int +pqt_put_timestamp(PGtypeArgs *args) +{ + return put_timestamp2(args, 0); +} + +int +pqt_put_timestamptz(PGtypeArgs *args) +{ + return put_timestamp2(args, 1); +} + +/* Also handles timestamptz */ +static int +get_timestamp2(PGtypeArgs *args, int withtz) +{ + DECLVALUE(args); + char tsbuf[8]; + double ts; + PGtimestamp *pgts = va_arg(args->ap, PGtimestamp *); + + CHKGETVALS(args, pgts); + + if (args->format == TEXTFMT) + { + double time; + double date; + int year; + + if (text2date(value, &pgts->date, args->fmtinfo->datestyle) == -1) + RERR(args, "invalid date format"); + + if (text2time(value, &pgts->time, withtz) == -1) + RERR(args, "invalid time format"); + + /* compute ts & epoch values. */ + time2t(NULL, &pgts->time, &time); + year = BC2YEAR(pgts->date.isbc, pgts->date.year); + date = date2j(year, pgts->date.mon+1, pgts->date.mday) - + POSTGRES_EPOCH_JDATE; + ts = (double) (date * SECS_PER_DAY + time); + + /* currently in localtime, convert to GMT */ + if (pgts->time.withtz) + ts -= (pgts->time.gmtoff); + + pgts->epoch = (PGint8) rint(ts - ((double) pgts->time.usec/1000000.0) + + (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + + return 0; + } + + pqt_swap8(tsbuf, value, 0); + + /* Convert 8 byte integer format to double precision. */ + if (args->fmtinfo->integer_datetimes) + { + PGint8 n = *(PGint8 *) tsbuf; + ts = (double)n / 1000000.0; + } + else + { + ts = *(double *) tsbuf; + } + + if (bin2pgts(ts, pgts, withtz) == -1) + RERR(args, "negative julian day detected"); + + return 0; +} + +int +pqt_get_timestamp(PGtypeArgs *args) +{ + return get_timestamp2(args, 0); +} + +int +pqt_get_timestamptz(PGtypeArgs *args) +{ + return get_timestamp2(args, 1); +} + +int +pqt_put_interval(PGtypeArgs *args) +{ + int day; + int month; + PGinterval *intvl = va_arg(args->ap, PGinterval *); + + PUTNULLCHK(args, intvl); + + month = intvl->years * MONTHS_PER_YEAR + intvl->mons; + day = intvl->days; + + if (args->fmtinfo->integer_datetimes) + { + PGint8 val = (PGint8) ((((((intvl->hours * PQT_INT64CONST(60)) + + intvl->mins) * PQT_INT64CONST(60)) + intvl->secs) * USECS_PER_SEC) + + intvl->usecs); + + pqt_swap8(args->put.out, &val, 1); + } + else + { + double val = (double) ((((intvl->hours * (double) MINS_PER_HOUR) + + intvl->mins) * (double) SECS_PER_MINUTE) + (double) intvl->secs + + ((double) intvl->usecs/1000000.0)); + + pqt_swap8(args->put.out, &val, 1); + } + + pqt_buf_putint4(args->put.out + 8, day); + pqt_buf_putint4(args->put.out + 12, month); + return 16; /* val(8) + day(4) + mon(4) */ + +} + +int +pqt_get_interval(PGtypeArgs *args) +{ + DECLVALUE(args); + double tval; + int mons; + int days; + char tvalbuf[8]; + PGinterval *interval = va_arg(args->ap, PGinterval *); + + CHKGETVALS(args, interval); + + if (args->format == TEXTFMT) + { + if (text2interval(value, interval)== -1) + RERR(args, "invalid interval format"); + return 0; + } + + pqt_swap8(tvalbuf, value, 0); + days = pqt_buf_getint4(value + 8); + mons = pqt_buf_getint4(value + 12); + + /* Convert 8 byte integer format to double precision. */ + if (args->fmtinfo->integer_datetimes) + { + PGint8 n = *(PGint8 *) tvalbuf; + tval = (double) n / 1000000.0; + } + else + { + tval = *(double *) tvalbuf; + } + + if (bin2pginterval(tval, days, mons, interval) == -1) + RERR(args, "binary interval conversion failed"); + return 0; +} + +void +PQlocalTZInfo(time_t *t, int *gmtoff, int *isdst, char **tzabbr) +{ +#if defined(HAVE_STRUCT_TM_TM_ZONE) || defined(HAVE_TM_ZONE) || \ + defined(HAVE_TZNAME) + time_t tbuf; + struct tm *loc; + struct tm buf; + + if (!t) + { + tbuf = time(NULL); + t = &tbuf; + } + +# ifdef HAVE_LOCALTIME_R + /* return value inconsistent. for instance: HP-UX 10 returns an int */ + loc = &buf; + localtime_r(t, loc); +# else + buf.tm_hour = 0; /* avoid compiler warning about unreferenced variable */ + loc = localtime(t); +# endif + + *gmtoff = 0; + *isdst = loc->tm_isdst==1 ? 1 : loc->tm_isdst==0 ? 0 : -1; + if (tzabbr) + *tzabbr = ""; + +# ifdef HAVE_STRUCT_TM_TM_GMTOFF + *gmtoff = (int) loc->tm_gmtoff; +# endif + +# if defined(HAVE_STRUCT_TM_TM_ZONE) || defined(HAVE_TM_ZONE) + if (tzabbr) + *tzabbr = (char *) loc->tm_zone; + + /* Use global timezone and tzname variables */ +# elif defined(HAVE_TZNAME) + *gmtoff = -((loc->tm_isdst > 0) ? pqt_timezone - SECS_PER_HOUR : + pqt_timezone); + if (tzabbr) + *tzabbr = pqt_tzname[(loc->tm_isdst > 0)]; + + /* When winapi is defined, msvc/mingw, tzname contains the windows + * timezone names, like "Eastern Daylight Time" instead of EDT. This + * function will map these names to the standard abbrev. + */ +# ifdef PQT_WINAPI + if (tzabbr) + *tzabbr = _win32_tzabbr(*tzabbr); +# endif +# endif /* HAVE_TZNAME */ + +#else + *gmtoff = 0; + *isdst = -1; + if (tzabbr) + *tzabbr = ""; +#endif /* HAVE_STRUCT_TM_TM_ZONE || HAVE_TM_ZONE || HAVE_TZNAME */ +} + +static char * +time2t(PGtypeArgs *args, PGtime *t, void *val) +{ + if (args && args->fmtinfo->integer_datetimes) + *(PGint8 *) val = (PGint8) (((((t->hour * MINS_PER_HOUR + t->min) * + SECS_PER_MINUTE) + t->sec) * USECS_PER_SEC) + t->usec); + else + *(double *) val = (double) (((t->hour * MINS_PER_HOUR + t->min) * + SECS_PER_MINUTE) + t->sec + (t->usec/1000000.0)); + return val; +} + +static int +text2date(const char *datestr, PGdate *date, const char *datestyle) +{ + int year; + + while (isspace((int) *datestr)) + ++datestr; + + errno = 0; + + /* ISO, SQL or German ('1997-12-17', '12/17/1997', '17.12.1997') */ + if (isdigit((int) *datestr)) + { + int n[3]; + + n[0] = (int) strtol(datestr, (char **) &datestr, 10); + if (errno || (*datestr!='-' && *datestr!='/' && *datestr!='.')) + return -1; + + n[1] = (int) strtol(datestr+1, (char **) &datestr, 10); + if (errno || (*datestr!='-' && *datestr!='/' && *datestr!='.')) + return -1; + + n[2] = (int) strtol(datestr+1, (char **) &datestr, 10); + if (errno) + return -1; + + if (!*datestyle || strstr(datestyle, "ISO") || strstr(datestyle, "YMD")) + { + date->year = n[0]; + date->mon = n[1]; + date->mday = n[2]; + } + else if (strstr(datestyle, "DMY")) + { + date->mday = n[0]; + date->mon = n[1]; + date->year = n[2]; + } + else /* MDY */ + { + date->mon = n[0]; + date->mday = n[1]; + date->year = n[2]; + } + + date->mon--; + } + /* Postgres style: 'Thu Dec 13 19:42:52.442126 2007 EST' */ + else + { + static char *monnames[] = {"jan", "feb", "mar", "apr", "may", + "jun", "jul", "aug", "sep", "oct","nov", "dec"}; + + int i; + + /* skip day of week name */ + if (!(datestr = strchr(datestr, ' '))) + return -1; + datestr++; + + /* datestr is the day DMY */ + if (isdigit((int) *datestr)) + { + date->mday = (int) strtol(datestr, (char **) &datestr, 10); + if (errno) + return -1; + + datestr++; + for (i=0; i < 12; i++) + { + if (pqt_strncasecmp(datestr, monnames[i], 3)==0) + { + date->mon = i; + break; + } + } + + if (i == 12) + return -1; + + datestr += 4; /* skip space after monname */ + } + /* datestr is the monname MDY */ + else + { + int i; + + for (i=0; i < 12; i++) + { + if (pqt_strncasecmp(datestr, monnames[i], 3)==0) + { + date->mon = i; + break; + } + } + + if (i == 12) + return -1; + + datestr += 4; /* skip space after monname */ + date->mday = (int) strtol(datestr, (char **) &datestr, 10); + if (errno) + return -1; + datestr++; /* space after month day */ + } + + /* skip time */ + if (!(datestr = strchr(datestr, ' '))) + return -1; + + date->year = (int) strtol(datestr, NULL, 10); + if (errno) + return -1; + } + + if (date->mday < 0 || date->mday > 31) + return -1; + + if (date->mon < 0 || date->mon > 11) + return -1; + + date->isbc = strstr(datestr, " BC") ? 1 : 0; + year = BC2YEAR(date->isbc, date->year); + date->jday = date2j(year, date->mon+1, date->mday); + date->wday = (date->jday + 1) % 7; + date->yday = date->jday - date2j(year, 1, 1); + return 0; +} + +/* timestr can be a valid timestamp[tz] or time[tz]. */ +static int +text2time(char *timestr, PGtime *time, int withtz) +{ + char *p; + + memset(time, 0, sizeof(PGtime)); + time->isdst = -1; + + if (!(p = strchr(timestr, ':'))) + return -1; + + timestr = p - 2; + errno = 0; + + time->hour = (int) strtol(timestr, ×tr, 10); + if (errno || *timestr != ':' || time->hour < 0 || time->hour > 23) + return -1; + + time->min = (int) strtol(timestr+1, ×tr, 10); + if (errno || *timestr != ':' || time->min < 0 || time->min > 59) + return -1; + + time->sec = (int) strtol(timestr+1, ×tr, 10); + if (errno || time->sec < 0 || time->sec > 59) + return -1; + + if (*timestr == '.') + { + int i=0; + char buf[7]; + + timestr++; + + /* Need 6 digits for usecs to work with strtol, so pad with zeros. */ + memset(buf, '0', 6); + buf[6] = 0; + while (isdigit((int) *timestr)) + buf[i++] = *timestr++; + + time->usec = (int) strtol(buf, NULL, 10); + if (errno || time->usec < 0 || time->usec > 999999) + return -1; + } + + /* no timezone present */ + if (!withtz) + return 0; + + + /* --------------------------------- + * Following ISO 8601, also detecting timezone abbrev. + */ + + time->withtz = 1; + + /* UTC, using 'Z' encoding '00:00:00Z' */ + if (*timestr == 'Z') + { + pqt_strcpy(time->tzabbr, sizeof(time->tzabbr), "UTC"); + timestr++; + } + /* have gmtoff */ + else if (*timestr=='-' || *timestr=='+') + { + int hour = 0; + int min = 0; + int sec = 0; + char sign = *timestr++; + char buf[3]; + + buf[0] = *timestr++; + buf[1] = *timestr++; + buf[2] = 0; + hour = (int) strtol(buf, NULL, 10); + if (errno) + return -1; + + /* +/-hh:mm:ss case (support seconds) */ + if (*timestr == ':') + timestr++; + + /* have minute field */ + if (isdigit((int) *timestr)) + { + buf[0] = *timestr++; + buf[1] = *timestr++; + min = (int) strtol(buf, NULL, 10); + if (errno) + return -1; + + if (*timestr == ':') + timestr++; + + /* have second field */ + if (isdigit((int) *timestr)) + { + buf[0] = *timestr++; + buf[1] = *timestr++; + sec = (int) strtol(buf, NULL, 10); + if (errno) + return -1; + } + } + + time->gmtoff = (hour * 3600) + (min * 60) + sec; + if (sign == '-') + time->gmtoff = -time->gmtoff; + + if (!ISVALIDGMTOFF(time->gmtoff)) + return -1; + } + + /* find the beginning of tzname */ + while (*timestr && !isalpha((int) *timestr)) + timestr++; + + if (*timestr) + { + /* find timezone abbrev end */ + if (!(p = strchr(timestr, ' '))) + p = timestr + strlen(timestr); + + memcpy(time->tzabbr, timestr, p - timestr); + time->tzabbr[p - timestr] = 0; + + /* BC is not a timezone, false-positive */ + if (strcmp(time->tzabbr, "BC")==0) + *time->tzabbr = 0; + } + + if (time->gmtoff != 0 && !*time->tzabbr) + gmtoff2name(time->gmtoff, time->tzabbr, sizeof(time->tzabbr)); + else if (time->gmtoff==0 && *time->tzabbr) + tzabbr2info(time->tzabbr, &time->gmtoff, &time->isdst); + + return 0; +} + +static int +text2interval(char *istr, PGinterval *interval) +{ + char *s; + PGtime pgtime; + int is_before = 0; + + errno = 0; + memset(interval, 0, sizeof(PGinterval)); + + is_before = strstr(istr, "ago") ? 1 : 0; + + if ((s = strstr(istr, " year"))) + { + for (s=s-1; s > istr && !isspace((int) *s); --s) ; + interval->years = (int)strtol(s, NULL, 10); + if (errno) + return -1; + if (!is_before) + is_before = interval->years < 0; + } + + if ((s = strstr(istr, " mon"))) + { + for (s=s-1; s > istr && !isspace((int) *s); --s) ; + interval->mons = (int)strtol(s, NULL, 10); + if (errno) + return -1; + if (!is_before) + is_before = interval->mons < 0; + } + + if ((s = strstr(istr, " day"))) + { + for (s=s-1; s > istr && !isspace((int) *s); --s) ; + interval->days = (int) strtol(s, NULL, 10); + if (errno) + return -1; + if (!is_before) + is_before = interval->days < 0; + } + + /* Means ISO DateStyle is in use, uses the 00:00:00 time format */ + if (text2time(istr, &pgtime, 0) == 0) + { + interval->usecs = pgtime.usec; + interval->secs = pgtime.sec; + interval->mins = pgtime.min; + interval->hours = pgtime.hour; + + /* text2time doesn't support negative values, detect is_before here. + * Find first ':' and subtract two-digit year + possible minus sign. + */ + if (!is_before) + is_before = (s = strchr(istr, ':')) && (*(s - 3) == '-'); + } + /* Means POSTGRES, SQL or GERMAN DateStyle is in use. */ + else + { + if ((s = strstr(istr, " hour"))) + { + for (s=s-1; s > istr && !isspace((int) *s); --s) ; + interval->hours = (int) strtol(s, NULL, 10); + if (errno) + return -1; + } + + if ((s = strstr(istr, " min"))) + { + for (s=s-1; s > istr && !isspace((int) *s); --s) ; + interval->mins = (int) strtol(s, NULL, 10); + if (errno) + return -1; + } + + if ((s = strstr(istr, " sec"))) + { + for (s=s-1; s > istr && !isspace((int) *s); --s) ; + interval->secs = (int) strtol(s, &s, 10); + if (errno) + return -1; + + /* usecs */ + if (*s == '.') + { + int i=0; + char buf[7]; + memset(buf, '0', 6); + buf[6] = 0; + for (s=s+1; isdigit((int) *s); s++) + buf[i++] = *s; + interval->usecs = atoi(buf); + } + } + } + + /* flip unit signs where still needed */ + if (is_before) + { + if (interval->years > 0) + interval->years = -interval->years; + if (interval->mons > 0) + interval->mons = -interval->mons; + if (interval->days > 0) + interval->days = -interval->days; + if (interval->hours > 0) + interval->hours = -interval->hours; + if (interval->mins > 0) + interval->mins = -interval->mins; + if (interval->secs > 0) + interval->secs = -interval->secs; + if (interval->usecs > 0) + interval->usecs = -interval->usecs; + } + + return 0; +} + +/* Uses ISO 8601 GMT+/-hhmmss notation. See tzabbr2info as well. */ +static void +gmtoff2name(int gmtoff, char *buf, size_t size) +{ + char sign; + int mins; + int secs; + + if (gmtoff < 0) + { + sign = '-'; + gmtoff = -gmtoff; + } + else + { + sign = '+'; + } + + mins = gmtoff % 3600; + secs = mins % 60; + + if (secs == 0) + pqt_snprintf(buf, size, "GMT%c%02d%02d", sign, gmtoff/3600, mins/60); + else + pqt_snprintf(buf, size, "GMT%c%02d%02d%02d", + sign, gmtoff/3600, mins/60, secs); +} + +static int +date2j(int y, int m, int d) +{ + int julian; + int century; + + if (m > 2) + { + m += 1; + y += 4800; + } + else + { + m += 13; + y += 4799; + } + + century = y / 100; + julian = y * 365 - 32167; + julian += y / 4 - century + century / 4; + julian += 7834 * m / 256 + d; + + return julian; +} + +static void +j2date(int jd, int *year, int *month, int *day) +{ + unsigned int julian; + unsigned int quad; + unsigned int extra; + int y; + + julian = jd; + julian += 32044; + quad = julian / 146097; + extra = (julian - quad * 146097) * 4 + 3; + julian += 60 + quad * 3 + extra / 146097; + quad = julian / 1461; + julian -= quad * 1461; + y = julian * 4 / 1461; + julian = ((y != 0) ? ((julian + 305) % 365) : ((julian + 306) % 366)) + + 123; + y += quad * 4; + *year = y - 4800; + quad = julian * 2141 / 65536; + *day = julian - 7834 * quad / 256; + *month = (quad + 10) % 12 + 1; +} + +static void +dt2time(double jd, int *hour, int *min, int *sec, double *fsec) +{ + *hour = (int) (jd / SECS_PER_HOUR); + jd -= (*hour) * SECS_PER_HOUR; + *min = (int) (jd / SECS_PER_MINUTE); + jd -= (*min) * SECS_PER_MINUTE; + *sec = (int) jd; + *fsec = jd - *sec; +} + +static int +bin2pgdate(int dval, PGdate *date) +{ + memset(date, 0, sizeof(PGdate)); + + j2date(dval + POSTGRES_EPOCH_JDATE, &date->year, &date->mon, &date->mday); + date->jday = date2j(date->year, date->mon, date->mday); + date->yday = date->jday - date2j(date->year, 1, 1); + date->wday = (date->jday + 1) % 7; + + if (date->year <= 0) + { + date->isbc = 1; + date->year = -(date->year-1); + } + + date->mon--; + return 0; +} + +/* gmtoff is seconds east of GMT, adjusted prior to call. */ +static int +bin2pgtime(double tval, int gmtoff, PGtime *time, int withtz) +{ + double rem; + + memset(time, 0, sizeof(PGtime)); + time->isdst = -1; /* no way of knowing */ + + if (withtz) + { + time->gmtoff = gmtoff; + gmtoff2name(time->gmtoff, time->tzabbr, sizeof(time->tzabbr)); + time->withtz = 1; + } + + RECALC: + rem = tval; + TMODULO(rem, time->hour, (double) SECS_PER_HOUR); + TMODULO(rem, time->min, (double) SECS_PER_MINUTE); + TMODULO(rem, time->sec, 1.0); + rem = TIMEROUND(rem); + + /* roundoff may need to propagate to higher-order fields */ + if (rem >= 1.0) + { + tval = ceil(tval); + goto RECALC; + } + + time->usec = (int) (rem * 1000000.0); + + return 0; +} + +static int +breakdown_time(double time, double *datep, PGdate *pgdate, + PGtime *pgtime, double *fsec) +{ + double date; + + TMODULO(time, date, (double) SECS_PER_DAY); + + if (time < 0) + { + time += SECS_PER_DAY; + date -= 1; + } + + /* add offset to go from J2000 back to standard Julian date */ + date += POSTGRES_EPOCH_JDATE; + + RECALC_DATE: + /* Julian day routine does not work for negative Julian days */ + if (date < 0 || date > (PGint8) INT_MAX) + return -1; + + j2date((int)date, &pgdate->year, &pgdate->mon, &pgdate->mday); + + RECALC_TIME: + dt2time(time, &pgtime->hour, &pgtime->min, &pgtime->sec, fsec); + *fsec = TSROUND(*fsec); + + /* roundoff may need to propagate to higher-order fields */ + if (*fsec >= 1.0) + { + time = ceil(time); + if (time >= (double) SECS_PER_DAY) + { + time = 0; + date += 1; + goto RECALC_DATE; + } + goto RECALC_TIME; + } + + if (datep) + *datep = date; + return 0; +} + +static int +bin2pgts(double ts, PGtimestamp *pgts, int withtz) +{ + double fsec; + double date; + double time; + + memset(pgts, 0, sizeof(PGtimestamp)); + pgts->time.isdst = -1; + + time = ts; + + /* server sent UTC, convert to localtime. Need to determine the + * gmtoff for the timestamp epoch value (daylight or not). This + * can differ from what zone the machine is in. If its July in + * NY but the timestamp is Jan, we need to apply EST to timestamp + * rather than EDT. + */ + if (withtz) + { + char *tzabbr; + time_t tepoch; + PGtime pgtime; + PGdate pgdate; + PGint8 t64 = (PGint8) rint(ts); + + pgts->epoch = (PGint8) rint((double) t64 + + (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + + /* Safe range for a time_t is 0 - INT_MAX. Some system don't support + * negative values at all. System with 64-bit time_t can support + * wider ranges, but there is no way of determining what that is. + * NOTE: 64-bit time_t systems aren't limited to 31-bit upper values. + */ + if (pgts->epoch < 0 || (sizeof(time_t)==4 && pgts->epoch > INT_MAX)) + { + struct tm tm; + + /* pull out the date and time parts so we can issue a mktime + * call, which gets us epoch. + */ + if (breakdown_time(ts, NULL, &pgdate, &pgtime, &fsec) < 0) + return -1; + + memset(&tm, 0, sizeof(struct tm)); + tm.tm_year = 70; /* out-of-safe-range values use 1970 */ + + /* Daylight time was first used by Germany April 30, 1916. So, + * we only recognize its existence after this date. + */ + if (pgdate.year >= 1916) + tm.tm_mon = pgdate.mon - 1; + else /* Ignore month before Apr 1916, use Jan for standard time */ + tm.tm_mon = 0; + + tm.tm_mday = pgdate.mday; + tm.tm_hour = pgtime.hour; + tm.tm_min = pgtime.min; + tm.tm_sec = pgtime.sec; + tm.tm_isdst = -1; + + tepoch = mktime(&tm); + } + else + { + tepoch = (time_t) pgts->epoch; + } + + /* Have an epoch value that can be used to get TZInfo */ + PQlocalTZInfo(&tepoch, &pgts->time.gmtoff, &pgts->time.isdst, &tzabbr); + pqt_strcpy(pgts->time.tzabbr, sizeof(pgts->time.tzabbr), tzabbr); + + /* adjust postgres timestamp by gmtoff. */ + time += pgts->time.gmtoff; + pgts->time.withtz = 1; + } + + /* breakdown the `localtime' adjusted timestamp */ + if (breakdown_time(time, &date, &pgts->date, &pgts->time, &fsec) < 0) + return -1; + + /* jullian day */ + pgts->date.jday = (int) date; + + /* get the epoch, more accurate than previous calculation */ + pgts->epoch = (PGint8) rint( + ts - fsec + (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + + /* NOTE: dbl_epsilon is too small "2.2204460492503131e-16" */ + pgts->time.usec = (int) (fsec * (1000000.0 + 1E-9)); + + /* use julian day calculations for wday and yday */ + pgts->date.wday = ((int) date + 1) % 7; + pgts->date.yday = (int) date - date2j(pgts->date.year, 1, 1); + + /* Adjust year if this is BC */ + if (pgts->date.year <= 0) + { + pgts->date.year = -(pgts->date.year-1); + pgts->date.isbc = 1; + } + + pgts->date.mon--; + return 0; +} + +static int +bin2pginterval(double tval, int days, int mons, PGinterval *interval) +{ + double time; + double tfrac; + + memset(interval, 0, sizeof(PGinterval)); + + interval->years = mons / MONTHS_PER_YEAR; + interval->mons = mons % MONTHS_PER_YEAR; + interval->days = days; + time = tval; + + RECALC: + TMODULO(time, tfrac, (double) SECS_PER_HOUR); + interval->hours = (int)tfrac; /* could overflow ... */ + + TMODULO(time, tfrac, (double) SECS_PER_MINUTE); + interval->mins = (int)tfrac; + + TMODULO(time, tfrac, 1.0); + interval->secs = (int)tfrac; + + time = TSROUND(time); + /* roundoff may need to propagate to higher-order fields */ + if (time >= 1.0) + { + time = ceil(tval); + goto RECALC; + } + + /* NOTE: dbl_epsilon is too small "2.2204460492503131e-16" */ + interval->usecs = (int) (time * (1000000.0 + 1E-9)); + + return 0; +} + + +/* Generated by the below query: + * + * SELECT '{"' || abbrev || '", ' || + * ((EXTRACT(HOUR FROM utc_offset) * 3600) + + * (EXTRACT(MINUTE FROM utc_offset) * 60) + + * EXTRACT(SECOND FROM utc_offset)) || + * ', ' || (CASE WHEN is_dst THEN 1 ELSE 0 END) || '},' + * FROM pg_timezone_abbrevs; + */ +struct _tzmap_ +{ + const char *abbrev; + int gmtoff; + unsigned char isdst; +}; + +static struct _tzmap_ tzmap[] = { + {"ACSST", 37800, 1}, + {"ACST", -14400, 1}, + {"ACT", -18000, 0}, + {"ADT", -10800, 1}, + {"AESST", 39600, 1}, + {"AEST", 36000, 0}, + {"AFT", 16200, 0}, + {"AKDT", -28800, 1}, + {"AKST", -32400, 0}, + {"ALMST", 25200, 1}, + {"ALMT", 21600, 0}, + {"AMST", 18000, 1}, + {"AMT", 14400, 0}, + {"ANAST", 46800, 1}, + {"ANAT", 43200, 0}, + {"ART", -10800, 0}, + {"AST", -14400, 0}, + {"AWSST", 32400, 1}, + {"AWST", 28800, 0}, + {"AZOST", 0, 1}, + {"AZOT", -3600, 0}, + {"AZST", 18000, 1}, + {"AZT", 14400, 0}, + {"BDST", 7200, 1}, + {"BDT", 21600, 0}, + {"BNT", 28800, 0}, + {"BORT", 28800, 0}, + {"BOT", -14400, 0}, + {"BRA", -10800, 0}, + {"BRST", -7200, 1}, + {"BRT", -10800, 0}, + {"BST", 3600, 1}, + {"BTT", 21600, 0}, + {"CADT", 37800, 1}, + {"CAST", 34200, 0}, + {"CCT", 28800, 0}, + {"CDT", -18000, 1}, + {"CEST", 7200, 1}, + {"CET", 3600, 0}, + {"CETDST", 7200, 1}, + {"CHADT", 49500, 1}, + {"CHAST", 45900, 0}, + {"CKT", 43200, 0}, + {"CLST", -10800, 1}, + {"CLT", -14400, 0}, + {"COT", -18000, 0}, + {"CST", -21600, 0}, + {"CXT", 25200, 0}, + {"DAVT", 25200, 0}, + {"DDUT", 36000, 0}, + {"EASST", -18000, 1}, + {"EAST", -21600, 0}, + {"EAT", 10800, 0}, + {"EDT", -14400, 1}, + {"EEST", 10800, 1}, + {"EET", 7200, 0}, + {"EETDST", 10800, 1}, + {"EGST", 0, 1}, + {"EGT", -3600, 0}, + {"EST", -18000, 0}, + {"FJST", -46800, 1}, + {"FJT", -43200, 0}, + {"FKST", -10800, 1}, + {"FKT", -14400, 0}, + {"FNST", -3600, 1}, + {"FNT", -7200, 0}, + {"GALT", -21600, 0}, + {"GAMT", -32400, 0}, + {"GEST", 14400, 1}, + {"GET", 10800, 0}, + {"GFT", -10800, 0}, + {"GILT", 43200, 0}, + {"GMT", 0, 0}, + {"GYT", -14400, 0}, + {"HKT", 28800, 0}, + {"HST", -36000, 0}, + {"ICT", 25200, 0}, + {"IOT", 21600, 0}, + {"IRKST", 32400, 1}, + {"IRKT", 28800, 0}, + {"IRT", 12600, 0}, + {"IST", 7200, 0}, + {"JAYT", 32400, 0}, + {"JST", 32400, 0}, + {"KDT", 36000, 1}, + {"KGST", 21600, 1}, + {"KGT", 18000, 0}, + {"KOST", 39600, 0}, + {"KRAST", 28800, 1}, + {"KRAT", 25200, 0}, + {"KST", 32400, 0}, + {"LHDT", 39600, 1}, + {"LHST", 37800, 0}, + {"LIGT", 36000, 0}, + {"LINT", 50400, 0}, + {"LKT", 21600, 0}, + {"MAGST", 43200, 1}, + {"MAGT", 39600, 0}, + {"MART", -34200, 0}, + {"MAWT", 21600, 0}, + {"MDT", -21600, 1}, + {"MEST", 7200, 1}, + {"MET", 3600, 0}, + {"METDST", 7200, 1}, + {"MEZ", 3600, 0}, + {"MHT", 43200, 0}, + {"MMT", 23400, 0}, + {"MPT", 36000, 0}, + {"MSD", 14400, 1}, + {"MSK", 10800, 0}, + {"MST", -25200, 0}, + {"MUT", 14400, 0}, + {"MVT", 18000, 0}, + {"MYT", 28800, 0}, + {"NDT", -9000, 1}, + {"NFT", -12600, 0}, + {"NOVST", 25200, 1}, + {"NOVT", 21600, 0}, + {"NPT", 20700, 0}, + {"NST", -12600, 0}, + {"NUT", -39600, 0}, + {"NZDT", 46800, 1}, + {"NZST", 43200, 0}, + {"NZT", 43200, 0}, + {"OMSST", 25200, 1}, + {"OMST", 21600, 0}, + {"PDT", -25200, 1}, + {"PET", -18000, 0}, + {"PETST", 46800, 1}, + {"PETT", 43200, 0}, + {"PGT", 36000, 0}, + {"PHOT", 46800, 0}, + {"PHT", 28800, 0}, + {"PKT", 18000, 0}, + {"PMDT", -7200, 1}, + {"PMST", -10800, 0}, + {"PONT", 39600, 0}, + {"PST", -28800, 0}, + {"PWT", 32400, 0}, + {"PYST", -10800, 1}, + {"PYT", -14400, 0}, + {"RET", 14400, 0}, + {"SADT", 37800, 1}, + {"SAST", 34200, 0}, + {"SCT", 14400, 0}, + {"TAHT", -36000, 0}, + {"TFT", 18000, 0}, + {"TJT", 18000, 0}, + {"TKT", -36000, 0}, + {"TMT", 18000, 0}, + {"TOT", 46800, 0}, + {"TRUT", 36000, 0}, + {"TVT", 43200, 0}, + {"UCT", 0, 0}, + {"ULAST", 32400, 1}, + {"ULAT", 28800, 0}, + {"UT", 0, 0}, + {"UTC", 0, 0}, + {"UYST", -7200, 1}, + {"UYT", -10800, 0}, + {"UZST", 21600, 1}, + {"UZT", 18000, 0}, + {"VET", -14400, 0}, + {"VLAST", 39600, 1}, + {"VLAT", 36000, 0}, + {"VUT", 39600, 0}, + {"WADT", 28800, 1}, + {"WAKT", 43200, 0}, + {"WAST", 25200, 0}, + {"WAT", 3600, 0}, + {"WDT", 32400, 1}, + {"WET", 0, 0}, + {"WETDST", 3600, 1}, + {"WFT", 43200, 0}, + {"WGST", -7200, 1}, + {"WGT", -10800, 0}, + {"YAKST", 36000, 1}, + {"YAKT", 32400, 0}, + {"YAPT", 36000, 0}, + {"YEKST", 21600, 1}, + {"YEKT", 18000, 0}, + {"Z", 0, 0}, + {"ZULU", 0, 0} +}; + +static void +tzabbr2info(const char *tzabbr, int *gmtoff, int *isdst) +{ + int i=0; + + *gmtoff = 0; + *isdst = -1; + + for (i=0; i < countof(tzmap); i++) + { + if (strcmp(tzabbr, tzmap[i].abbrev)==0) + { + *gmtoff = tzmap[i].gmtoff; + *isdst = (int) tzmap[i].isdst; + break; + } + } +} + + +#ifdef PQT_WINAPI +/* ############################################################## + TIME ZONE MAPPING + + Windows doesn't use any abbreviated time zone names, like + EST for Eastern Standard Time. The below list was required + to offer these abbreviations. + + This list was compiled from: + + http://www.date-time-zone.com/ + + which is the same list used by Java. It is a mapping from the windows + registry: "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" + to the Olson Timezone Database: + + http://www.twinsun.com/tz/tz-link.htm + + NOTE: The below two time zones had a different display name in the + windows registry. The list was adjusted. + + Signapore Standard Time - Malay Peninsula Standard Time + Israel Standard Time - Jerusalem Standard Time +*/ + +static struct { + const char *abbr; + const char *fullname; +} _tzmap[] = { + {"EST", "AUS Eastern Standard Time" }, + {"EST", "AUS Eastern Daylight Time" }, + {"AFT", "Afghanistan Standard Time" }, + {"AKST", "Alaskan Standard Time" }, + {"AKDT", "Alaskan Daylight Time" }, + {"AST", "Arab Standard Time" }, + {"GST", "Arabian Standard Time" }, + {"AST", "Arabic Standard Time" }, + {"ADT", "Arabic Daylight Time" }, + {"AST", "Atlantic Standard Time" }, + {"ADT", "Atlantic Daylight Time" }, + {"AZOT", "Azores Standard Time" }, + {"AZOST", "Azores Daylight Time" }, + {"CST", "Canada Central Standard Time" }, + {"MDT", "Canada Central Daylight Time" }, + {"CVT", "Cape Verde Standard Time" }, + {"CVST", "Cape Verde Daylight Time" }, + {"GET", "Caucasus Standard Time" }, + {"GEST", "Caucasus Daylight Time" }, + {"CST", "Cen. Australia Standard Time" }, + {"CST", "Cen. Australia Daylight Time" }, + {"CST", "Central Standard Time" }, + {"CDT", "Central Daylight Time" }, + {"CST", "Central Standard Time (Mexico)" }, + {"CDT", "Central Daylight Time (Mexico)" }, + {"CST", "Central America Standard Time" }, + {"CDT", "Central America Daylight Time" }, + {"BDT", "Central Asia Standard Time" }, + {"CET", "Central Europe Standard Time" }, + {"CEST", "Central Europe Daylight Time" }, + {"CET", "Central European Standard Time" }, + {"CEST", "Central European Daylight Time" }, + {"MAGT", "Central Pacific Standard Time" }, + {"MAGST", "Central Pacific Daylight Time" }, + {"CST", "China Standard Time" }, + {"CDT", "China Daylight Time" }, + {"GMT+12", "Dateline Standard Time" }, + {"EAT", "E. Africa Standard Time" }, + {"EST", "E. Australia Standard Time" }, + {"EST", "E. Australia Daylight Time" }, + {"EET", "E. Europe Standard Time" }, + {"EEST", "E. Europe Daylight Time" }, + {"BRT", "E. South America Standard Time" }, + {"BRST", "E. South America Daylight Time" }, + {"EST", "Eastern Standard Time" }, + {"EDT", "Eastern Daylight Time" }, + {"EET", "Egypt Standard Time" }, + {"EEST", "Egypt Daylight Time" }, + {"YEKT", "Ekaterinburg Standard Time" }, + {"YEKST", "Ekaterinburg Daylight Time" }, + {"EET", "FLE Standard Time" }, + {"EEST", "FLE Daylight Time" }, + {"FJT", "Fiji Standard Time" }, + {"FJST", "Fiji Daylight Time" }, + {"GMT", "GMT Standard Time" }, + {"BST", "GMT Daylight Time" }, + {"EET", "GTB Standard Time" }, + {"EEST", "GTB Daylight Time" }, + {"WGT", "Greenland Standard Time" }, + {"WGST", "Greenland Daylight Time" }, + {"WET", "Greenwich Standard Time" }, + {"WEST", "Greenwich Daylight Time" }, + {"HST", "Hawaiian Standard Time" }, + {"HPT", "Hawaiian Daylight Time" }, + {"IST", "India Standard Time" }, + {"IST", "India Daylight Time" }, + {"IRST", "Iran Standard Time" }, + {"IRDT", "Iran Daylight Time" }, + {"IST", "Jerusalem Standard Time" }, + {"IDT", "Jerusalem Daylight Time" }, + {"KST", "Korea Standard Time" }, + {"KDT", "Korea Daylight Time" }, + {"CST", "Mexico Standard Time" }, + {"CDT", "Mexico Daylight Time" }, + {"MST", "Mexico Standard Time 2", }, + {"MDT", "Mexico Daylight Time 2", }, + {"FNT", "Mid-Atlantic Standard Time" }, + {"FNST", "Mid-Atlantic Daylight Time" }, + {"MST", "Mountain Standard Time" }, + {"MDT", "Mountain Daylight Time" }, + {"MST", "Mountain Standard Time (Mexico)" }, + {"MDT", "Mountain Daylight Time (Mexico)" }, + {"MMT", "Myanmar Standard Time" }, + {"NOVT", "N. Central Asia Standard Time" }, + {"NOVST", "N. Central Asia Daylight Time" }, + {"NPT", "Nepal Standard Time" }, + {"NZST", "New Zealand Standard Time" }, + {"NZDT", "New Zealand Daylight Time" }, + {"NST", "Newfoundland Standard Time" }, + {"NDT", "Newfoundland Daylight Time" }, + {"KRAT", "North Asia Standard Time" }, + {"KRAST", "North Asia Daylight Time" }, + {"IRKT", "North Asia East Standard Time" }, + {"IRKST", "North Asia East Daylight Time" }, + {"PST", "Pacific Standard Time" }, + {"PDT", "Pacific Daylight Time" }, + {"CLT", "Pacific SA Standard Time" }, + {"CLST", "Pacific SA Daylight Time" }, + {"CET", "Romance Standard Time" }, + {"CEST", "Romance Daylight Time" }, + {"MSK", "Russian Standard Time" }, + {"MSD", "Russian Daylight Time" }, + {"ART", "SA Eastern Standard Time" }, + {"ARST", "SA Eastern Daylight Time" }, + {"COT", "SA Pacific Standard Time" }, + {"COST", "SA Pacific Daylight Time" }, + {"VET", "SA Western Standard Time" }, + {"ICT", "SE Asia Standard Time" }, + {"WST", "Samoa Standard Time" }, + {"SGT", "Malay Peninsula Standard Time" }, + {"MALST", "Malay Peninsula Daylight Time" }, + {"SAST", "South Africa Standard Time" }, + {"SAST", "South Africa Daylight Time" }, + {"IST", "Sri Lanka Standard Time" }, + {"IST", "Sri Lanka Daylight Time" }, + {"CST", "Taipei Standard Time" }, + {"CDT", "Taipei Daylight Time" }, + {"EST", "Tasmania Standard Time" }, + {"EST", "Tasmania Daylight Time" }, + {"JST", "Tokyo Standard Time" }, + {"JDT", "Tokyo Daylight Time" }, + {"TOT", "Tonga Standard Time" }, + {"TOST", "Tonga Daylight Time" }, + {"EST", "US Eastern Standard Time" }, + {"EDT", "US Eastern Daylight Time" }, + {"MST", "US Mountain Standard Time" }, + {"MDT", "US Mountain Daylight Time" }, + {"VLAT", "Vladivostok Standard Time" }, + {"VLAST", "Vladivostok Daylight Time" }, + {"WST", "W. Australia Standard Time" }, + {"WST", "W. Australia Daylight Time" }, + {"WAT", "W. Central Africa Standard Time" }, + {"CET", "W. Europe Standard Time" }, + {"CEST", "W. Europe Daylight Time" }, + {"PKT", "West Asia Standard Time" }, + {"PKST", "West Asia Daylight Time" }, + {"ChST", "West Pacific Standard Time" }, + {"YAKT", "Yakutsk Standard Time" }, + {"YAKST", "Yakutsk Daylight Time" } +}; + +static char * +_win32_tzabbr(const char *fullname) +{ + int i; + static char empty_string[1] = {0}; + + for (i=0; i < countof(tzmap); i++) + if (pqt_strcasecmp(fullname, _tzmap[i].fullname)==0) + return (char *) _tzmap[i].abbr; + + return empty_string; +} + +#endif + diff --git a/source/src/error.c b/source/src/error.c new file mode 100644 index 0000000..3e9d8a8 --- /dev/null +++ b/source/src/error.c @@ -0,0 +1,330 @@ + +/* + * error.c + * The functions in this file represent the libpqtypes error + * system. It offers the API user the ability to set/get errors. + * The error system uses a per-thread global error, implemented + * using TLS (via declspec(thread) or pthread TLS). For + * non-PQT_THREAD_SAFE builds, a static buffer is used. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +/* Cannot use allocated memory for the pqterr_t members. This is + * because windows has no way of freeing per thread allocated + * memory automatically, other than DllMain which won't work for + * a third-party library such as libpqtypes. All error messages + * and fields are truncated to fit their buffers via pqt_strcpy(). + */ +typedef struct +{ + /* Room for user message and a full libpq message */ + char msg[1024 + 8192]; + + /* Error Fields (a little less than 8K for all) */ + char severity[16]; + char sqlstate[16]; + char message_primary[2048]; + char message_detail[1024]; + char message_hint[512]; + char stmt_position[16]; + char internal_position[16]; + char internal_query[2048]; + char context[2048]; + char source_file[256]; + char source_line[16]; + char source_function[80]; +} pqterr_t; + +/* non thread-safe, use a static */ +#ifndef PQT_THREAD_SAFE +static pqterr_t lasterr = {{0}}; + +/* MSVC always uses __declspec(thread) */ +#elif defined(PQT_MSVC) +static __declspec(thread) pqterr_t lasterr = {0}; + +/* Unix-variant, MinGW or Cygwin. */ +#else +#include +static pthread_key_t tlskey_lasterr; + +/* When the thread dies, this will get called. */ +static void tls_free_lasterr(void *value) +{ + if (value) + { + free(value); + pthread_setspecific(tlskey_lasterr, NULL); + + } +} + +/* called before main. This attribute is available since gcc 2.7.0. + * It replaced the obsolete _init ... destructor replaced _fini. + * We used to use pthread_once, but solaris doesn't support this + * in older versions (its simply a stub returning 0, ouch!). + */ +void __attribute__((constructor)) __InItErRoRkEy__(void) +{ + pthread_key_create(&tlskey_lasterr, tls_free_lasterr); +} +#endif + +static void +vseterror(const char *format, va_list ap, int append); + +static pqterr_t * +geterr(void) +{ + pqterr_t *err = NULL; + + /* Non thread-safe mode or windows msvc. */ +#if !defined(PQT_THREAD_SAFE) || defined(PQT_MSVC) + err = &lasterr; + + /* systems requiring pthread TLS keys */ +#else + + /* System is using FSU Threads, like SCO OpenServer 5, which + * has minor prototype differences. + */ +# ifdef PTHREAD_FSU + pthread_getspecific(tlskey_lasterr, (void **) &err); +# else + err = (pqterr_t *) pthread_getspecific(tlskey_lasterr); +# endif + + if (!err) + { + err = (pqterr_t *) malloc(sizeof(pqterr_t)); + if (!err) + return NULL; + memset(err, 0, sizeof(pqterr_t)); + pthread_setspecific(tlskey_lasterr, err); + } + +#endif + + return err; +} + +char * +PQgeterror(void) +{ + static char _empty[1] = {0}; + pqterr_t *err = geterr(); + return err ? err->msg : _empty; +} + +void +PQseterror(const char *format, ...) +{ + /* clear error message by passing in NULL, PQseterror(NULL).*/ + if (!format) + { + pqterr_t *err = geterr(); + + if (err && *err->msg) + { + /* do not memset the entire pqterr_t structure, its around 20K and + * causes performance problems when putting lots of array elements, + * since PQputvf always clears the error PQseterror(NULL) before + * attempting to put data. + */ + *err->msg = 0; + *err->severity = 0; + *err->sqlstate = 0; + *err->message_primary = 0; + *err->message_detail = 0; + *err->message_hint = 0; + *err->stmt_position = 0; + *err->internal_position = 0; + *err->internal_query = 0; + *err->context = 0; + *err->source_file = 0; + *err->source_line = 0; + *err->source_function = 0; + } + } + else + { + va_list ap; + va_start(ap, format); + vseterror(format, ap, FALSE); + } +} + +char * +PQgetErrorField(int fieldcode) +{ + pqterr_t *err = geterr(); + + if (!err) + return NULL; + + switch (fieldcode) + { + case PG_DIAG_SEVERITY: + return err->severity; + + case PG_DIAG_SQLSTATE: + return err->sqlstate; + + case PG_DIAG_MESSAGE_PRIMARY: + return err->message_primary; + + case PG_DIAG_MESSAGE_DETAIL: + return err->message_detail; + + case PG_DIAG_MESSAGE_HINT: + return err->message_hint; + + case PG_DIAG_STATEMENT_POSITION: + return err->stmt_position; + + case PG_DIAG_INTERNAL_POSITION: + return err->internal_position; + + case PG_DIAG_INTERNAL_QUERY: + return err->internal_query; + + case PG_DIAG_CONTEXT: + return err->context; + + case PG_DIAG_SOURCE_FILE: + return err->source_file; + + case PG_DIAG_SOURCE_LINE: + return err->source_line; + + case PG_DIAG_SOURCE_FUNCTION: + return err->source_function; + + default: + return NULL; + } +} + +/* Used by pqt_setresultfields */ +#define geterrfield(buf, name) do{ \ + if ((value = PQresultErrorField(res, name))) \ + pqt_strcpy(buf, sizeof(buf), value); \ + else \ + *buf = 0; \ +}while (0) + +void +pqt_setresultfields(const PGresult *res) +{ + char *value; + pqterr_t *err = geterr(); + + if (!err) + return; + + geterrfield(err->severity, PG_DIAG_SEVERITY); + geterrfield(err->sqlstate, PG_DIAG_SQLSTATE); + geterrfield(err->message_primary, PG_DIAG_MESSAGE_PRIMARY); + geterrfield(err->message_detail, PG_DIAG_MESSAGE_DETAIL); + geterrfield(err->message_hint, PG_DIAG_MESSAGE_HINT); + geterrfield(err->stmt_position, PG_DIAG_STATEMENT_POSITION); + geterrfield(err->internal_position, PG_DIAG_INTERNAL_POSITION); + geterrfield(err->internal_query, PG_DIAG_INTERNAL_QUERY); + geterrfield(err->context, PG_DIAG_CONTEXT); + geterrfield(err->source_file, PG_DIAG_SOURCE_FILE); + geterrfield(err->source_line, PG_DIAG_SOURCE_LINE); + geterrfield(err->source_function, PG_DIAG_SOURCE_FUNCTION); +} + +/* errorf() callback for PGtypeArgs, see PQputf() and PQgetf(). + * Always returns -1. + */ +int +pqt_argserrorf(PGtypeArgs *args, const char *format, ...) +{ + va_list ap; + char fqtn[200]; + + if (!args || !format || !*format) + return -1; + + pqt_fqtn(fqtn, sizeof(fqtn), args->typhandler->typschema, + args->typhandler->typname); + + /* put the header */ + PQseterror("%s[pos:%d] - ", fqtn, args->typpos); + + /* append message from type handler */ + va_start(ap, format); + vseterror(format, ap, TRUE); + return -1; +} + +static void +vseterror(const char *format, va_list ap, int append) +{ + int n; + int curlen = 0; + int size; + va_list ap2; + char *msg = NULL; + pqterr_t *err = geterr(); + + if (!err) + return; + + if (append) + curlen = (int) strlen(err->msg); + else + *err->msg = 0; + + va_copy(ap2, ap); + n = pqt_vsnprintf(err->msg + curlen, sizeof(err->msg) - curlen, format, ap2); + va_end(ap2); + + if (n > -1) + return; + + /* pqterr_t msg buffer is too small for the complete message. We have + * use a temporary buffer to get a successful sprintf so we can + * pqt_strcpy() the result; which truncates to fit. + */ + size = (int) sizeof(err->msg) * 2; + if (!(msg = (char *) malloc(size))) + return; + + while (1) + { + char *p; + + va_copy(ap2, ap); + n = pqt_vsnprintf(msg + curlen, size - curlen, format, ap2); + va_end(ap2); + + /* success */ + if (n > -1) + break; + + /* need more space */ + n = size * 2; + if (!(p = pqt_realloc(msg, n))) + { + /* we're here because sprintf failed, don't trust buffer contents */ + *msg = 0; + break; + } + + msg = p; + size = n; + } + + pqt_strcpy(err->msg, sizeof(err->msg), msg); + free(msg); +} + diff --git a/source/src/events.c b/source/src/events.c new file mode 100644 index 0000000..ea30927 --- /dev/null +++ b/source/src/events.c @@ -0,0 +1,173 @@ + +/* + * events.c + * The libpq PGEventProc implementation. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +static PGtypeData * +allocTypeData(PGconn *conn); + +static void +freeTypeData(PGtypeData *typeData); + +/* Deprecated, use PQinitTypes instead */ +int +PQtypesRegister(PGconn *conn) +{ + return PQinitTypes(conn); +} + +int +pqt_eventproc(PGEventId id, void *info, void *passThrough) +{ + switch (id) + { + case PGEVT_REGISTER: + { + PGEventRegister *e = (PGEventRegister *) info; + void *data = allocTypeData(e->conn); + if (!data) + return FALSE; + PQsetInstanceData((PGconn *) e->conn, pqt_eventproc, data); + break; + } + + case PGEVT_CONNRESET: + { + /* No special handling for PGEVT_CONNRESET. Previously, types were + * automatically re-registered but this idea fails miserably during + * asynchronous resets. Yanked in favor of using the following + * call sequence: PQresetXXX, PQclearTypes, PQregisterTypes. + */ + break; + } + + case PGEVT_CONNDESTROY: + { + PGEventConnDestroy *e = (PGEventConnDestroy *) info; + freeTypeData((PGtypeData *) PQinstanceData(e->conn, pqt_eventproc)); + break; + } + + case PGEVT_RESULTCREATE: + { + PGtypeData *resData; + PGEventResultCreate *e = (PGEventResultCreate *) info; + PGtypeData *connData = (PGtypeData *) PQinstanceData( + e->conn, pqt_eventproc); + + if (!connData || !(resData = allocTypeData(e->conn))) + return FALSE; + + /* copy type handlers from PGconn's typeData */ + if (connData->typhcnt > 0) + { + resData->typhandlers = pqt_duphandlers( + connData->typhandlers, connData->typhcnt); + + if (resData->typhandlers) + resData->typhcnt = connData->typhcnt; + } + + /* copy type specs from PGconn's typeData */ + if (connData->typspeccnt > 0) + { + resData->typspecs = pqt_dupspecs( + connData->typspecs, connData->typspeccnt); + + if (resData->typspecs) + resData->typspeccnt = connData->typspeccnt; + } + + PQresultSetInstanceData((PGresult *) e->result, pqt_eventproc, resData); + break; + } + + case PGEVT_RESULTCOPY: + { + PGtypeData *destData; + PGEventResultCopy *e = (PGEventResultCopy *) info; + PGtypeData *srcData = (PGtypeData *) PQresultInstanceData( + e->src, pqt_eventproc); + + if (!srcData || !(destData = allocTypeData(NULL))) + return FALSE; + + memcpy(&destData->fmtinfo, &srcData->fmtinfo, sizeof(PGtypeFormatInfo)); + + /* copy type handlers from PGresult's typeData */ + if (srcData->typhcnt > 0) + { + destData->typhandlers = pqt_duphandlers( + srcData->typhandlers, srcData->typhcnt); + + if (destData->typhandlers) + destData->typhcnt = srcData->typhcnt; + } + + /* copy type specs from PGresult's typeData */ + if (srcData->typspeccnt > 0) + { + destData->typspecs = pqt_dupspecs( + srcData->typspecs, srcData->typspeccnt); + + if (destData->typspecs) + destData->typspeccnt = srcData->typspeccnt; + } + + PQresultSetInstanceData(e->dest, pqt_eventproc, destData); + break; + } + + case PGEVT_RESULTDESTROY: + { + PGEventResultDestroy *e = (PGEventResultDestroy *) info; + freeTypeData((PGtypeData *) PQresultInstanceData( + e->result, pqt_eventproc)); + break; + } + } + + return TRUE; +} + +static PGtypeData * +allocTypeData(PGconn *conn) +{ + PGtypeData *typeData = (PGtypeData *) malloc(sizeof(PGtypeData)); + + if (typeData) + { + memset(typeData, 0, sizeof(PGtypeData)); + + /* get type formatting info from conn */ + if (conn) + pqt_getfmtinfo(conn, &typeData->fmtinfo); + } + + return typeData; +} + +static void +freeTypeData(PGtypeData *typeData) +{ + if (typeData) + { + pqt_cleartypes(typeData); + + pqt_freespecs(typeData->typspecs, typeData->typspeccnt); + typeData->typspecs = NULL; + typeData->typspeccnt = 0; + typeData->typspecmax = 0; + + free(typeData); + } +} + diff --git a/source/src/exec.c b/source/src/exec.c new file mode 100644 index 0000000..834a382 --- /dev/null +++ b/source/src/exec.c @@ -0,0 +1,562 @@ + +/* + * exec.c + * Query execution and data retrieval functions. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +/* + * Each param value requires an oid, ptr and 2 ints (oid, value, length, + * format). This makes the maximum size of a param 20 bytes. A stack size + * of 4k would allow for 204 query params. If more params are needed, + * heap memory is used. Needing more than 204 param values in a query + * is very rare, although possible. + */ +#define PARAM_STACKSIZE 4096 + +#define BUILD_ARRAYS(rettype) \ + rettype r; \ + char *buf = NULL; \ + Oid *oids = NULL; \ + char **vals = NULL; \ + int *lens = NULL; \ + int *fmts = NULL; \ + int vcnt = 0; \ + char stackbuffer[PARAM_STACKSIZE]; \ +\ + PQseterror(NULL); \ + if (!conn) \ + { \ + PQseterror("PGconn cannot be NULL"); \ + return (rettype)(0); \ + } \ +\ + if (param) \ + { \ + buf = stackbuffer; \ + if (!buildArrays(param, &buf, &oids, &vals, &lens, &fmts)) \ + return (rettype) (0); \ + vcnt = param->vcnt; \ + } + +#define RETURN_RESULT \ + if (param) \ + { \ + if (buf && buf != stackbuffer) \ + free(buf); \ + } \ + return r + +static int +buildArrays(PGparam *param, char **buf, Oid **oids, + char ***vals, int **lens, int **fmts); + +static PGresult * +copyExecError(PGconn *conn, PGresult *r); + +static const char * +getCommand(PGconn *conn, PGparam *param, const char *command); + +static int +_execvf(PGconn *conn, const char *cmdspec, va_list ap, PGresult **resp); + +int +PQgetf(const PGresult *res, int tup_num, const char *format, ...) +{ + int n; + va_list ap; + + va_start(ap, format); + n = PQgetvf(res, tup_num, format, ap); + va_end(ap); + + return n; +} + +int +PQgetvf(const PGresult *res, int tup_num, const char *format, va_list ap) +{ + int r; + PGtypeHandler *h; + PGtypeArgs args; + int typpos = 0; + int flags; + Oid ftype; + PGtypeData *resData; + PGtypeSpec *spec = NULL; + char tmp[200]; + + PQseterror(NULL); + + if (!res) + { + PQseterror("PGresult cannot be NULL"); + return FALSE; + } + + if (!(resData = (PGtypeData *) PQresultInstanceData(res, pqt_eventproc))) + { + PQseterror("PGresult at %p has no event data", res); + return FALSE; + } + + va_copy(args.ap, ap); + + /* "@name" format, lookup typeSpec in cache */ + if(format && *format == '@') + { + spec = pqt_getspec(resData->typspecs, resData->typspeccnt, format + 1); + + /* If we didn't find a type spec, this is an error. A format string + * with a '@' as its first character is reserved. + */ + if (!spec) + { + PQseterror("No such prepared specifier name: '%s'", format + 1); + va_end(args.ap); + return FALSE; + } + } + + while (format && *format) + { + if (spec) + { + /* done, no more handlers in cached spec string. */ + if (typpos == spec->idcnt) + break; + + h = pqt_gethandlerbyid(resData->typhandlers, resData->typhcnt, + spec->idlist[typpos]); + + /* should be an unusual, or a "will never happen", situation */ + if (!h) + { + va_end(args.ap); + PQseterror("Unknown type handler id at position %d", typpos+1); + return FALSE; + } + + flags = (int) spec->flags[typpos]; + typpos++; + } + else + { + format = pqt_parse(format, resData->typhandlers, resData->typhcnt, + NULL, 0, &h, NULL, &typpos, &flags); + + if (!format) + { + va_end(args.ap); + return FALSE; + } + + if (!h) + continue; + } + + if (flags & TYPFLAG_BYNAME) + args.get.field_num = PQfnumber(res, va_arg(args.ap, const char *)); + else + args.get.field_num = va_arg(args.ap, int); + + /* simplify life for handlers by checking getvalue's return here. */ + if (args.get.field_num < 0 || + !PQgetvalue(res, tup_num, args.get.field_num)) + { + PQseterror( + "Invalid tup_num[%d].field_num[%d] (position %d)", + tup_num, args.get.field_num, typpos); + va_end(args.ap); + return FALSE; + } + + ftype = PQftype(res, args.get.field_num); + if (((flags & TYPFLAG_ARRAY) && ftype != h->typoid_array) || + (!(flags & TYPFLAG_ARRAY) && ftype != h->typoid)) + { + Oid oid = (flags & TYPFLAG_ARRAY) ? h->typoid_array : h->typoid; + PQseterror( + "Trying to get type %u '%s' but server returned %u (position %d)", + oid, pqt_fqtn(tmp, sizeof(tmp), h->typschema, h->typname), + ftype, typpos); + va_end(args.ap); + return FALSE; + } + + args.is_put = 0; + args.get.result = (PGresult *) res; + args.format = PQfformat(res, args.get.field_num); + args.fmtinfo = &resData->fmtinfo; + args.get.tup_num = tup_num; + args.is_ptr = (flags & TYPFLAG_POINTER) ? 1 : 0; + args.typpos = typpos; + args.typhandler = h; + args.errorf = pqt_argserrorf; + args.super = pqt_argssuper; + + if (flags & TYPFLAG_ARRAY) + r = pqt_get_array(&args); + else + r = h->typget(&args); + + if (r == -1) + { + va_end(args.ap); + return FALSE; + } + } + + va_end(args.ap); + + return TRUE; +} + +/* -------------------------------- + * Exec and Send functions + */ + +PGresult * +PQexecf(PGconn *conn, const char *cmdspec, ...) +{ + va_list ap; + PGresult *res; + + va_start(ap, cmdspec); + res = PQexecvf(conn, cmdspec, ap); + va_end(ap); + + return res; +} + +PGresult * +PQexecvf(PGconn *conn, const char *cmdspec, va_list ap) +{ + PGresult *res; + (void) _execvf(conn, cmdspec, ap, &res); + return res; +} + +int +PQsendf(PGconn *conn, const char *cmdspec, ...) +{ + int n; + va_list ap; + + va_start(ap, cmdspec); + n = PQsendvf(conn, cmdspec, ap); + va_end(ap); + + return n; +} + +int +PQsendvf(PGconn *conn, const char *cmdspec, va_list ap) +{ + return _execvf(conn, cmdspec, ap, NULL); +} + +PGresult * +PQparamExec(PGconn *conn, PGparam *param, const char *command, + int resultFormat) +{ + BUILD_ARRAYS(PGresult *); + + command = getCommand(conn, param, command); + if (!command) + { + r = NULL; + } + else + { + r = PQexecParams(conn, command, vcnt, oids, + (const char *const * ) vals, lens, fmts, resultFormat); + + pqt_setresultfields(r); + r = copyExecError(conn, r); + } + + RETURN_RESULT; +} + +int +PQparamSendQuery(PGconn *conn, PGparam *param, const char *command, + int resultFormat) +{ + BUILD_ARRAYS(int); + + command = getCommand(conn, param, command); + if (!command) + { + r = FALSE; + } + else + { + r = PQsendQueryParams(conn, command, vcnt, oids, + (const char *const * ) vals, lens, fmts, resultFormat); + + if (!r) + PQseterror("PGconn: %s", PQerrorMessage(conn)); + } + + RETURN_RESULT; +} + +PGresult * +PQparamExecPrepared(PGconn *conn, PGparam *param, const char *stmtName, + int resultFormat) +{ + BUILD_ARRAYS(PGresult *); + + r = PQexecPrepared(conn, stmtName, vcnt, (const char *const * ) vals, + lens, fmts, resultFormat); + + pqt_setresultfields(r); + r = copyExecError(conn, r); + + RETURN_RESULT; +} + +int +PQparamSendQueryPrepared(PGconn *conn, PGparam *param, const char *stmtName, + int resultFormat) +{ + BUILD_ARRAYS(int); + + r = PQsendQueryPrepared(conn, stmtName, vcnt, + (const char *const * ) vals, lens, fmts, resultFormat); + + if (!r) + PQseterror("PGconn: %s", PQerrorMessage(conn)); + + RETURN_RESULT; +} + +/* Called by PQexecvf and PQsendvf. When resp is NULL, PQparamSendQuery + * is used to execute the command. Otherwise, PQparamExec is used. The + * return value is always zero when resp is not NULL. When resp is NULL, + * the return value is zero for error and non-zero for success (identical + * to PQparamSendQuery). + */ +static int +_execvf(PGconn *conn, const char *cmdspec, va_list ap, PGresult **resp) +{ + int retval = 0; + size_t stmt_len=0; + char buffer[8192]; /* could be larger these days but be conservative */ + char *stmt = NULL; + PGparam *param = NULL; + + if (resp) + *resp = NULL; + + if(!conn) + { + PQseterror("PGconn cannot be NULL"); + return FALSE; + } + + if(!cmdspec || !*cmdspec) + { + PQseterror("cmdspec cannot be NULL or an empty string"); + return FALSE; + } + + /* No stmt buf required for preapred type specs */ + if (*cmdspec != '@') + { + /* The resulting parameterized command is guarenteed to be smaller + * than the cmdspec. When needed, enlarge stmt buf to cmdspec length. + */ + stmt_len = strlen(cmdspec) + 1; + + /* stack buffer is too small, use heap */ + if (stmt_len > sizeof(buffer)) + { + if (!(stmt = (char *) malloc(stmt_len))) + { + PQseterror(PQT_OUTOFMEMORY); + return FALSE; + } + } + else + { + stmt = buffer; + stmt_len = sizeof(buffer); + } + } + + if ((param = PQparamCreate(conn))) + { + if (PQputvf(param, stmt, stmt_len, cmdspec, ap)) + { + const char *s = stmt ? stmt : cmdspec; + + if (resp) + *resp = PQparamExec(conn, param, s, 1); + else + retval = PQparamSendQuery(conn, param, s, 1); + } + + PQparamClear(param); + } + + if (stmt && stmt != buffer) + free(stmt); + + return retval; +} + +/* + * Assigns param values to param arrays, for use with postgres + * parameter API. 'buf' is expected to be PARAM_STACKSIZE bytes. If more + * memory is required, memory is allocated and assigned to *buf, which + * must be freed by caller. To determine if *buf was allocated, compare + * its address to the initially provided stack address. + * Returns 1 on success and 0 on error. + */ +static int +buildArrays(PGparam *param, char **buf, Oid **oids, + char ***vals, int **lens, int **fmts) +{ + int n; + + /* no params to assign */ + if (param->vcnt == 0) + return 1; + + /* required memory size for the 4 param arrays */ + n = (int) ((sizeof(void *) * param->vcnt) + /* values */ + ((sizeof(int) * 2) * param->vcnt) + /* lengths and formats */ + (sizeof(Oid) * param->vcnt)); /* oids */ + + /* required memory is too large for stack buffer, get some heap */ + if (n > PARAM_STACKSIZE) + { + char *p; + + if (!(p = (char *) malloc(n))) + { + PQseterror(PQT_OUTOFMEMORY); + return 0; + } + + *buf = p; + } + + /* give arrays memory from buffer, which could be stack or heap. */ + *vals = (char **) *buf; + *lens = (int *) (*buf + (sizeof(void *) * param->vcnt)); + *fmts = (*lens) + param->vcnt; + *oids = (Oid *) ((*fmts) + param->vcnt); + + /* loop param values and assign value, length, format + * and oid to arrays. + */ + for (n=0; n < param->vcnt; n++) + { + (*oids)[n] = param->vals[n].oid; + (*vals)[n] = param->vals[n].data; + (*lens)[n] = param->vals[n].datal; + (*fmts)[n] = param->vals[n].format; + } + + return 1; +} + +static PGresult * +copyExecError(PGconn *conn, PGresult *r) +{ + if (!r) + { + PQseterror("PGconn: %s", PQerrorMessage(conn)); + return NULL; + } + + switch (PQresultStatus(r)) + { + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + case PGRES_EMPTY_QUERY: + break; + + default: + { + PQseterror("PGresult: %s", PQresultErrorMessage(r)); + PQclear(r); + r = NULL; + break; + } + } + + return r; +} + +/* Using the param is preferred when both conn and param are provided. + * The conn is there in case the exec has no parameters, NULL param. + */ +static const char * +getCommand(PGconn *conn, PGparam *param, const char *command) +{ + PGtypeSpec *spec; + int typspeccnt = 0; + PGtypeSpec *typspecs = NULL; + + if (!command) + { + PQseterror("command to execute cannot be NULL"); + return NULL; + } + + if (*command != '@') + return command; + + if (param) + { + typspecs = param->typspecs; + typspeccnt = param->typspeccnt; + } + + /* Try to get instance data from the conn */ + if (!typspecs || typspeccnt == 0) + { + PGtypeData *data = PQinstanceData(conn, pqt_eventproc); + + if (!data) + { + PQseterror("PGconn at %p has no event data", conn); + return NULL; + } + + typspecs = data->typspecs; + typspeccnt = data->typspeccnt; + } + + spec = pqt_getspec(typspecs, typspeccnt, command + 1); + + /* If we didn't find a type spec, this is an error. A format string + * with an '@' as its first character is reserved. + */ + if (!spec) + { + PQseterror("No such prepared specifier name: '%s'", command + 1); + return NULL; + } + + /* make sure type spec was prepared with a statement */ + if (!spec->stmt || !*spec->stmt) + { + PQseterror("Prepared specifier name '%s' has no statement", command + 1); + return NULL; + } + + return (const char *) spec->stmt; +} + + diff --git a/source/src/geo.c b/source/src/geo.c new file mode 100644 index 0000000..7beecf2 --- /dev/null +++ b/source/src/geo.c @@ -0,0 +1,391 @@ + +/* + * geo.c + * Type handler for the geometric data types. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +static int text2point(PGpoint *pt, char *text, char **endptr); +static int text2points(PGtypeArgs *args, PGpoint **pts, int *npts); +static int bin2points(PGtypeArgs *args, char *valp, int ptcnt, + PGpoint **pts, int *npts); +static int putpoints(PGtypeArgs *args, int npts, PGpoint *pts, + int is_path, int closed); + +int +pqt_put_point(PGtypeArgs *args) +{ + unsigned int *buf; + PGpoint *pt = va_arg(args->ap, PGpoint *); + + PUTNULLCHK(args, pt); + + buf = (unsigned int *) args->put.out; + pqt_swap8(buf, &pt->x, 1); + pqt_swap8(buf + 2, &pt->y, 1); + return 16; +} + +int +pqt_get_point(PGtypeArgs *args) +{ + DECLVALUE(args); + PGpoint *pt = va_arg(args->ap, PGpoint *); + + CHKGETVALS(args, pt); + + if (args->format == TEXTFMT) + { + if (!text2point(pt, value, NULL)) + RERR_STR2INT(args); + + return 0; + } + + pqt_swap8(&pt->x, (unsigned int *) value, 0); + pqt_swap8(&pt->y, ((unsigned int *) (value)) + 2, 0); + return 0; +} + +int +pqt_put_lseg(PGtypeArgs *args) +{ + unsigned int *buf; + PGlseg *lseg = va_arg(args->ap, PGlseg *); + + PUTNULLCHK(args, lseg); + + buf = (unsigned int *) args->put.out; + pqt_swap8(buf, &lseg->pts[0].x, 1); + pqt_swap8(buf + 2, &lseg->pts[0].y, 1); + pqt_swap8(buf + 4, &lseg->pts[1].x, 1); + pqt_swap8(buf + 6, &lseg->pts[1].y, 1); + return 32; +} + +int +pqt_get_lseg(PGtypeArgs *args) +{ + DECLVALUE(args); + unsigned int *v; + PGlseg *lseg = va_arg(args->ap, PGlseg *); + + CHKGETVALS(args, lseg); + + if (args->format == TEXTFMT) + { + PGpoint *pts = (PGpoint *)lseg; + + if (*value++ != '[' || + !text2point(pts, value, &value) || + *value++ != ',' || + !text2point(pts + 1, value, &value) || + *value != ']') + RERR_STR2INT(args); + + return 0; + } + + v = (unsigned int *) value; + pqt_swap8(&lseg->pts[0].x, v, 0); + pqt_swap8(&lseg->pts[0].y, v + 2, 0); + pqt_swap8(&lseg->pts[1].x, v + 4, 0); + pqt_swap8(&lseg->pts[1].y, v + 6, 0); + return 0; +} + +int +pqt_put_box(PGtypeArgs *args) +{ + unsigned int *buf; + PGbox *box = va_arg(args->ap, PGbox *); + + PUTNULLCHK(args, box); + + buf = (unsigned int *) args->put.out; + pqt_swap8(buf, &box->high.x, 1); + pqt_swap8(buf + 2, &box->high.y, 1); + pqt_swap8(buf + 4, &box->low.x, 1); + pqt_swap8(buf + 6, &box->low.y, 1); + return 32; +} + +int +pqt_get_box(PGtypeArgs *args) +{ + DECLVALUE(args); + unsigned int *v; + PGbox *box = va_arg(args->ap, PGbox *); + + CHKGETVALS(args, box); + + if (args->format == TEXTFMT) + { + PGpoint *pts = (PGpoint *)box; + + if (!text2point(pts, value, &value) || + *value++ != ',' || + !text2point(pts + 1, value, NULL)) + RERR_STR2INT(args); + + return 0; + } + + v = (unsigned int *) value; + pqt_swap8(&box->high.x, v, 0); + pqt_swap8(&box->high.y, v + 2, 0); + pqt_swap8(&box->low.x, v + 4, 0); + pqt_swap8(&box->low.y, v + 6, 0); + return 0; +} + +int +pqt_put_circle(PGtypeArgs *args) +{ + unsigned int *buf; + PGcircle *circle = va_arg(args->ap, PGcircle *); + + PUTNULLCHK(args, circle); + + buf = (unsigned int *) args->put.out; + pqt_swap8(buf, &circle->center.x, 1); + pqt_swap8(buf + 2, &circle->center.y, 1); + pqt_swap8(buf + 4, &circle->radius, 1); + return 24; +} + +int +pqt_get_circle(PGtypeArgs *args) +{ + DECLVALUE(args); + unsigned int *v; + PGcircle *circle = va_arg(args->ap, PGcircle *); + + CHKGETVALS(args, circle); + + if (args->format == TEXTFMT) + { + if (*value++ != '<' || + !text2point((PGpoint *)circle, value, &value) || + *value++ != ',' || + !pqt_text_to_float8(&circle->radius, value, &value) || + *value != '>') + RERR_STR2INT(args); + + return 0; + } + + v = (unsigned int *) value; + pqt_swap8(&circle->center.x, v, 0); + pqt_swap8(&circle->center.y, v + 2, 0); + pqt_swap8(&circle->radius, v + 4, 0); + return 0; +} + +int +pqt_put_path(PGtypeArgs *args) +{ + PGpath *path = va_arg(args->ap, PGpath *); + PUTNULLCHK(args, path); + return putpoints(args, path->npts, path->pts, 1, path->closed ? 1 : 0); +} + +int +pqt_get_path(PGtypeArgs *args) +{ + DECLVALUE(args); + PGpath *path = va_arg(args->ap, PGpath *); + + CHKGETVALS(args, path); + + if (args->format == TEXTFMT) + { + path->closed = *value == '(' ? 1 : 0; + return text2points(args, &path->pts, &path->npts); + } + + path->closed = *value ? 1 : 0; + value++; + + return bin2points(args, + value + sizeof(int), /* beginning of point array */ + pqt_buf_getint4(value), /* number of points */ + &path->pts, &path->npts); +} + +int +pqt_put_polygon(PGtypeArgs *args) +{ + PGpolygon *polygon = va_arg(args->ap, PGpolygon *); + PUTNULLCHK(args, polygon); + return putpoints(args, polygon->npts, polygon->pts, 0, 0); +} + +int +pqt_get_polygon(PGtypeArgs *args) +{ + DECLVALUE(args); + PGpolygon *polygon = va_arg(args->ap, PGpolygon *); + + CHKGETVALS(args, polygon); + + if (args->format == TEXTFMT) + return text2points(args, &polygon->pts, &polygon->npts); + + return bin2points(args, + value + sizeof(int), /* beginning of point array */ + pqt_buf_getint4(value), /* number of points */ + &polygon->pts, + &polygon->npts); +} + + +static int +putpoints(PGtypeArgs *args, int npts, PGpoint *pts, + int is_path, int closed) +{ + int i; + int datal; + int hdr = (int) sizeof(int); + char *out; + + /* pts is for a path, include 1 byte open/closed flag */ + if (is_path) + hdr++; + + /* length of binary formated path */ + datal = (npts * sizeof(PGpoint)) + hdr; + + /* make sure out is large enough */ + if (args->put.expandBuffer(args, datal) == -1) + return -1; + + out = args->put.out; + if (is_path) + *out++ = closed ? 1 : 0; /* path open/closed flag */ + + /* write the number of points as an int32 */ + pqt_buf_putint4(out, npts); + out += 4; + + /* assign points to the data 'out' buffer */ + for (i=0; i < npts; i++) + { + pqt_swap8(out, &pts[i].x, 1); + out += sizeof(double); + + pqt_swap8(out, &pts[i].y, 1); + out += sizeof(double); + } + + return datal; +} + +static int +text2points(PGtypeArgs *args, PGpoint **pts, int *npts) +{ + DECLVALUE(args); + char *s; + int cnt = 0; + PGpoint *p = NULL; + + *pts = NULL; + *npts = 0; + + if (*value != '(' && *value != '[') + RERR(args, "malformed point array"); + + /* get the number of points by counting the '(' */ + for (s=value+1; *s; s++) + { + if (*s == '(') + { + if (!(s = strchr(s, ')'))) /* skip point contents */ + break; + cnt++; + } + } + + if (cnt == 0) + return 0; /* empty point list */ + + p = (PGpoint *) PQresultAlloc((PGresult *) args->get.result, + cnt * sizeof(PGpoint)); + if (!p) + RERR_MEM(args); + + for (cnt=0; *++value; ) + { + if (!text2point(&p[cnt++], value, &value)) + RERR_STR2INT(args); + + /* done */ + if (*value != ',') + break; + } + + *pts = p; + *npts = cnt; + return 0; +} + +static int +bin2points(PGtypeArgs *args, char *valp, int ptcnt, + PGpoint **pts, int *npts) +{ + int i; + PGpoint *p; + + *pts = NULL; + *npts = 0; + + if (ptcnt == 0) + return 0; + + p = (PGpoint *) PQresultAlloc((PGresult *) args->get.result, + ptcnt * sizeof(PGpoint)); + + if (!p) + RERR_MEM(args); + + for (i=0; i < ptcnt; i++) + { + pqt_swap8(&p[i].x, valp, 0); + valp += sizeof(double); + + pqt_swap8(&p[i].y, valp, 0); + valp += sizeof(double); + } + + *pts = p; + *npts = ptcnt; + return 0; +} + +static int +text2point(PGpoint *pt, char *text, char **endptr) +{ + if (*text++ != '(') + return 0; + + if (!pqt_text_to_float8(&pt->x, text, &text) || *text++ != ',') + return 0; + + if (!pqt_text_to_float8(&pt->y, text, &text) || *text++ != ')') + return 0; + + if (endptr) + *endptr = text; + return 1; +} + + + + + diff --git a/source/src/getaddrinfo.h b/source/src/getaddrinfo.h new file mode 100644 index 0000000..6814db4 --- /dev/null +++ b/source/src/getaddrinfo.h @@ -0,0 +1,146 @@ + +/* + * Copyright (c) 2003-2008, PostgreSQL Global Development Group + * $PostgreSQL: pgsql/src/include/getaddrinfo.h + */ + +#if defined(__CYGWIN__) || (defined(HAVE_CONFIG_H) && \ + !defined(HAVE_GETADDRINFO)) + +#ifndef GETADDRINFO_H +#define GETADDRINFO_H + +#include +#define _XOPEN_SOURCE_EXTENDED 1 +#include + + +/* Various macros that ought to be in , but might not be */ + +#ifndef EAI_FAIL +#define EAI_BADFLAGS (-1) +#define EAI_NONAME (-2) +#define EAI_AGAIN (-3) +#define EAI_FAIL (-4) +#define EAI_FAMILY (-6) +#define EAI_SOCKTYPE (-7) +#define EAI_SERVICE (-8) +#define EAI_MEMORY (-10) +#define EAI_SYSTEM (-11) +#endif /* !EAI_FAIL */ + +#ifndef AI_PASSIVE +#define AI_PASSIVE 0x0001 +#endif + +#ifndef AI_NUMERICHOST +/* + * some platforms don't support AI_NUMERICHOST; define as zero if using + * the system version of getaddrinfo... + */ +#if defined(HAVE_STRUCT_ADDRINFO) && defined(HAVE_GETADDRINFO) +#define AI_NUMERICHOST 0 +#else +#define AI_NUMERICHOST 0x0004 +#endif +#endif + +#ifndef NI_NUMERICHOST +#define NI_NUMERICHOST 1 +#endif +#ifndef NI_NUMERICSERV +#define NI_NUMERICSERV 2 +#endif + +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#ifndef NI_MAXSERV +#define NI_MAXSERV 32 +#endif + +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE + +#ifndef HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE___SS_FAMILY +#define ss_family __ss_family +#else +#error struct sockaddr_storage does not provide an ss_family member +#endif +#endif + +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE___SS_LEN +#define ss_len __ss_len +#define HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN 1 +#endif +#else /* !HAVE_STRUCT_SOCKADDR_STORAGE */ + +/* Define a struct sockaddr_storage if we don't have one. */ + +#ifndef __CYGWIN__ +struct sockaddr_storage +{ + union + { + struct sockaddr sa; /* get the system-dependent fields */ + long long int ss_align; /* ensures struct is properly aligned */ + char ss_pad[128]; /* ensures struct has desired size */ + } ss_stuff; +}; +#endif + +#define ss_family ss_stuff.sa.sa_family +/* It should have an ss_len field if sockaddr has sa_len. */ +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +#define ss_len ss_stuff.sa.sa_len +#define HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN 1 +#endif +#endif /* HAVE_STRUCT_SOCKADDR_STORAGE */ + +#ifndef HAVE_STRUCT_ADDRINFO + +struct addrinfo +{ + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; + +#endif /* HAVE_STRUCT_ADDRINFO */ + + + +/* Rename private copies per comments above */ +#ifdef getaddrinfo +#undef getaddrinfo +#endif + +#ifdef freeaddrinfo +#undef freeaddrinfo +#endif + +#ifdef gai_strerror +#undef gai_strerror +#endif + +#ifdef getnameinfo +#undef getnameinfo +#endif + +extern int getaddrinfo(const char *node, const char *service, + const struct addrinfo * hints, struct addrinfo ** res); +extern void freeaddrinfo(struct addrinfo * res); +extern const char *gai_strerror(int errcode); +extern int getnameinfo(const struct sockaddr * sa, int salen, + char *node, int nodelen, + char *service, int servicelen, int flags); + +#endif /* GETADDRINFO_H */ + +#endif /* HAVE_GETADDRINFO */ + diff --git a/source/src/handler.c b/source/src/handler.c new file mode 100644 index 0000000..8d908ad --- /dev/null +++ b/source/src/handler.c @@ -0,0 +1,1077 @@ + +/* + * handler.c + * Type handler management functions, the core of the handler system. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +/* ------------------------- + * Built-in handler defaults: + * base_id, nattrs, freeAttDescs, attDescsBuf, attDescs + */ +#define __HANDLER_DEFAULTS__ -1, 0, 0, {{0}}, NULL + +/* id, schema, typname, typlen, oid, arroid, typput, typget, defaults */ +static PGtypeHandler pg_handlers[] = { + /* character types */ + {0, "pg_catalog", "char", 1, CHAROID, 1002, pqt_put_char, + pqt_get_char, __HANDLER_DEFAULTS__}, + {1, "pg_catalog", "varchar", -1, VARCHAROID, 1015, pqt_put_text, + pqt_get_text, __HANDLER_DEFAULTS__}, /* supports ptr */ + {2, "pg_catalog", "bpchar", -1, BPCHAROID, 1014, pqt_put_text, + pqt_get_text, __HANDLER_DEFAULTS__}, /* supports ptr */ + {3, "pg_catalog", "text", -1, TEXTOID, 1009, pqt_put_text, + pqt_get_text, __HANDLER_DEFAULTS__}, /* supports ptr */ + + /* boolean types */ + {4, "pg_catalog", "bool", 1, BOOLOID, 1000, pqt_put_bool, + pqt_get_bool, __HANDLER_DEFAULTS__}, + + /* numeric types */ + {5, "pg_catalog", "int2", 2, INT2OID, 1005, pqt_put_int2, + pqt_get_int2, __HANDLER_DEFAULTS__}, + {6, "pg_catalog", "int4", 4, INT4OID, 1007, pqt_put_int4, + pqt_get_int4, __HANDLER_DEFAULTS__}, + {7, "pg_catalog", "int8", 8, INT8OID, 1016, pqt_put_int8, + pqt_get_int8, __HANDLER_DEFAULTS__}, + {8, "pg_catalog", "float4", 4, FLOAT4OID, 1021, pqt_put_float4, + pqt_get_float4, __HANDLER_DEFAULTS__}, + {9, "pg_catalog", "float8", 8, FLOAT8OID, 1022, pqt_put_float8, + pqt_get_float8, __HANDLER_DEFAULTS__}, + {10, "pg_catalog", "numeric", -1, NUMERICOID, 1231, pqt_put_numeric, + pqt_get_numeric, __HANDLER_DEFAULTS__}, + + /* bytea types */ + {11, "pg_catalog", "bytea", -1, BYTEAOID, 1001, pqt_put_bytea, + pqt_get_bytea, __HANDLER_DEFAULTS__}, /* supports ptr */ + + /* geometric types */ + {12, "pg_catalog", "point", 16, POINTOID, 1017, pqt_put_point, + pqt_get_point, __HANDLER_DEFAULTS__}, + {13, "pg_catalog", "lseg", 32, LSEGOID, 1018, pqt_put_lseg, + pqt_get_lseg, __HANDLER_DEFAULTS__}, + {14, "pg_catalog", "box", 32, BOXOID, 1020, pqt_put_box, + pqt_get_box, __HANDLER_DEFAULTS__}, + {15, "pg_catalog", "circle", 24, CIRCLEOID, 719, pqt_put_circle, + pqt_get_circle, __HANDLER_DEFAULTS__}, + {16, "pg_catalog", "path", -1, PATHOID, 1019, pqt_put_path, + pqt_get_path, __HANDLER_DEFAULTS__}, + {17, "pg_catalog", "polygon", -1, POLYGONOID, 1027, pqt_put_polygon, + pqt_get_polygon, __HANDLER_DEFAULTS__}, + + /* monetary types */ + {18, "pg_catalog", "money", 8, CASHOID, 791, pqt_put_money, + pqt_get_money, __HANDLER_DEFAULTS__}, + + /* network address typess */ + {19, "pg_catalog", "inet", -1, INETOID, 1041, pqt_put_inet, + pqt_get_inet, __HANDLER_DEFAULTS__}, + {20, "pg_catalog", "cidr", -1, CIDROID, 651, pqt_put_inet, + pqt_get_cidr, __HANDLER_DEFAULTS__}, + {21, "pg_catalog", "macaddr", 6, MACADDROID, 1040, pqt_put_macaddr, + pqt_get_macaddr, __HANDLER_DEFAULTS__}, + + /* date & time types */ + {22, "pg_catalog", "time", 8, TIMEOID, 1183, pqt_put_time, + pqt_get_time, __HANDLER_DEFAULTS__}, + {23, "pg_catalog", "timetz", 12, TIMETZOID, 1270, pqt_put_timetz, + pqt_get_timetz, __HANDLER_DEFAULTS__}, + {24, "pg_catalog", "date", 4, DATEOID, 1182, pqt_put_date, + pqt_get_date, __HANDLER_DEFAULTS__}, + {25, "pg_catalog", "timestamp", 8, TIMESTAMPOID, 1115, pqt_put_timestamp, + pqt_get_timestamp, __HANDLER_DEFAULTS__}, + {26, "pg_catalog", "timestamptz", 8, TIMESTAMPTZOID, 1185, + pqt_put_timestamptz, pqt_get_timestamptz, __HANDLER_DEFAULTS__}, + {27, "pg_catalog", "interval", 16, INTERVALOID, 1187, pqt_put_interval, + pqt_get_interval, __HANDLER_DEFAULTS__}, + + /* object identifier types */ + {28, "pg_catalog", "oid", 4, OIDOID, 1028, pqt_put_int4, + pqt_get_int4, __HANDLER_DEFAULTS__}, + + /* UUID types */ + {29, "pg_catalog", "uuid", 16, UUIDOID, 2951, pqt_put_uuid, + pqt_get_uuid, __HANDLER_DEFAULTS__}, + + /* Record types (composites) */ + {30, "pg_catalog", "record", -1, RECORDOID, 0, pqt_put_record, + pqt_get_record, __HANDLER_DEFAULTS__}, + + /* pqt types */ + {31, "pqt", "str", -1, InvalidOid, 0, pqt_put_str, + NULL, __HANDLER_DEFAULTS__}, /* supports ptr */ + {32, "pqt", "null", -1, InvalidOid, 0, pqt_put_null, + NULL, __HANDLER_DEFAULTS__}, + + /* more character types */ + {33, "pg_catalog", "name", -1, NAMEOID, 1003, pqt_put_text, + pqt_get_text, __HANDLER_DEFAULTS__} /* supports ptr */ +}; + +static int +expandHandlers(PGtypeData *typeData); + +static PGrecordAttDesc * +initAttDescs(PGtypeHandler *h, char *attrs); + +static char * +parseType(const char *spec, char *schema, char *typname, int typpos); + +static int +getTypeParams(PGconn *conn, PGregisterType *types, int count, + PGarray *names, PGarray *schemas); + +static int +checkTypeLookups(PGresult *res, PGregisterType *types, int count); + +/* If res is NULL, non-blocking send is used, otherwise a blocking + * exec is issued and *res contains the result. Returns zero on + * error and non-zero on success. + */ +static int +performRegisterQuery(PGconn *conn, int which, PGregisterType *types, + int count, PGresult **res); + +/* Called by PQregisterTypes() for each type provided. */ +static int +registerSubClass(PGtypeData *connData, const char *type_name, + PGtypeProc typput, PGtypeProc typget); + +int +PQinitTypes(PGconn *conn) +{ + return PQregisterEventProc(conn, pqt_eventproc, "pqtypes", NULL); +} + +/* Deprecated */ +int +PQregisterSubClasses(PGconn *conn, PGregisterType *types, int count) +{ + return PQregisterTypes(conn, PQT_SUBCLASS, types, count, 0); +} + +/* Deprecated */ +int +PQregisterUserDefinedTypes(PGconn *conn, PGregisterType *types, int count) +{ + return PQregisterTypes(conn, PQT_USERDEFINED, types, count, 0); +} + +/* Deprecated */ +int +PQregisterComposites(PGconn *conn, PGregisterType *types, int count) +{ + return PQregisterTypes(conn, PQT_COMPOSITE, types, count, 0); +} + +int +PQregisterTypes(PGconn *conn, int which, PGregisterType *types, + int count, int async) +{ + int n = FALSE; + + PQseterror(NULL); + + if (!conn) + { + PQseterror("PGconn cannot be NULL"); + return FALSE; + } + + if (!types) + { + PQseterror("PGregisterType[] cannot be NULL"); + return FALSE; + } + + if (count < 0) + { + PQseterror("PGregisterType[] count cannot be less than zero"); + return FALSE; + } + + /* nothing to do, silently ignore it */ + if (count == 0) + return TRUE; + + if (which == PQT_SUBCLASS) + { + int i; + PGtypeData *connData; + + if (!(connData = (PGtypeData *) PQinstanceData(conn, pqt_eventproc))) + { + PQseterror("PGconn is missing event data"); + return FALSE; + } + + for (i=0; i < count; i++) + { + n = registerSubClass(connData, types[i].typname, + types[i].typput, types[i].typget); + + if (!n) + return FALSE; + } + } + else + { + PGresult *res = NULL; + + n = performRegisterQuery(conn, which, types, count, async ? NULL : &res); + + /* If not async, register the result and clear it. */ + if (n && !async) + { + n = PQregisterResult(conn, which, types, count, res); + PQclear(res); + } + } + + return n; +} + +int +PQregisterResult(PGconn *conn, int which, PGregisterType *types, + int count, PGresult *res) +{ + int i; + PGtypeData *connData; + char typname[PQT_MAXIDLEN + 1]; + char typschema[PQT_MAXIDLEN + 1]; + /* inherit typput and typget from record type */ + PGtypeHandler *h_rec = pqt_gethandler(NULL, 0, "pg_catalog", "record"); + + PQseterror(NULL); + + if (!conn) + { + PQseterror("PGconn cannot be NULL"); + return FALSE; + } + + if (!res) + { + PQseterror("PGresult cannot be NULL"); + return FALSE; + } + + if (which == PQT_SUBCLASS) + { + PQseterror("Cannot call PQregisterResult for a subclass registration."); + return FALSE; + } + + if (!(connData = (PGtypeData *) PQinstanceData(conn, pqt_eventproc))) + { + PQseterror("PGconn is missing event data"); + return FALSE; + } + + if (!types) + { + PQseterror("PGregisterType[] cannot be NULL"); + return FALSE; + } + + if (count < 0) + { + PQseterror("PGregisterType[] count cannot be less than zero"); + return FALSE; + } + + if(!checkTypeLookups(res, types, count)) + return FALSE; + + for (i=0; i < PQntuples(res); i++) + { + int flags; + PGint2 typlen; + PGtypeHandler *h; + + if (which == PQT_USERDEFINED && !types[i].typput && !types[i].typget) + { + PQseterror("Must provide a put and/or a get routine: '%s'", + types[i].typname); + return FALSE; + } + + /* make sure conn's type handlers array is large enough */ + if (!expandHandlers(connData)) + return FALSE; + + /* create the handler */ + h = &connData->typhandlers[connData->typhcnt]; + memset(h, 0, sizeof(PGtypeHandler)); + + if (!PQgetf(res, i, "%oid %oid %int2", 1, &h->typoid, + 2, &h->typoid_array, 3, &typlen)) + { + return FALSE; + } + + h->id = connData->typhcnt + countof(pg_handlers); + h->typlen = (int) typlen; + h->base_id = -1; + + if (which == PQT_USERDEFINED) + { + h->typput = types[i].typput; + h->typget = types[i].typget; + } + else + { + h->typput = h_rec->typput; + h->typget = h_rec->typget; + } + + /* parse out type and schema names again */ + (void ) pqt_parsetype(types[i].typname, typschema, typname, &flags, 1); + pqt_strcpy(h->typschema, sizeof(h->typschema), typschema); + pqt_strcpy(h->typname, sizeof(h->typname), typname); + + /* Process composite attributes */ + if(which == PQT_COMPOSITE) + { + PGtext attrs; + int nattrs; + PGrecordAttDesc *attDescs; + + if (!PQgetf(res, i, "%text", 4, &attrs)) + { + return FALSE; + } + + if (!(attDescs = initAttDescs(h, attrs))) + return FALSE; + + for (nattrs=0; *attrs; nattrs++) + { + char *p; + char *name; + int len; + + /* Attribute Text Encoding: + * "attoid,attlen,atttypmod,name_hex attoid,etc..." + */ + + attDescs[nattrs].attoid = (int) strtol(attrs, &attrs, 10); + attDescs[nattrs].attlen = (int) strtol(attrs + 1, &attrs, 10); + attDescs[nattrs].atttypmod = (int) strtol(attrs + 1, &attrs, 10); + + /* skip comma before name */ + attrs++; + + /* attribute name in hex */ + if (!(p = strchr(attrs, ' '))) + p = attrs + strlen(attrs); /* last attr, point at NUL */ + + /* truncate name if it exceeds buffer */ + len = (int) (p - attrs); + if (len >= (int) sizeof(attDescs[nattrs].attname)) + len = (int) (sizeof(attDescs[nattrs].attname) - 1); + + /* hex decode and copy */ + for (name = attDescs[nattrs].attname; attrs < p; attrs += 2) + *name++ = (char) (pqt_hex_to_dec(attrs[0]) << 4) + | pqt_hex_to_dec(attrs[1]); + *name = 0; + } + + h->nattrs = nattrs; + h->attDescs = attDescs; + } + + connData->typhcnt++; + } + + return TRUE; +} + +int +PQclearTypes(PGconn *conn) +{ + PGtypeData *connData; + + if (!conn) + { + PQseterror("PGconn cannot be NULL"); + return FALSE; + } + + if (!(connData = (PGtypeData *) PQinstanceData(conn, pqt_eventproc))) + { + PQseterror("PGconn is missing event data"); + return FALSE; + } + + pqt_cleartypes(connData); + + return TRUE; +} + +/* Do not call when hcnt is 0. This returns NULL when malloc fails. + * Passing in 0 could only do the same thing (ambiguos), so it is simply + * not handled and may dump core. + */ +PGtypeHandler * +pqt_duphandlers(PGtypeHandler *handlers, int hcnt) +{ + int i; + PGtypeHandler *h = (PGtypeHandler *) malloc(hcnt * sizeof(PGtypeHandler)); + + if (!h) + return NULL; + + /* In the most common cases, this is the total cost of the dup. + * Previously, the handler had 4 inner strings that required a deep + * copy. Surprisingly, this had a noticeable overhead. This was + * solved by using fixed length buffers in the type handler struct. + * Also, a fixed length attDescs buffer was added to avoid having + * to perform a deep copy for the common cases, 16 or less attrs. + */ + memcpy(h, handlers, hcnt * sizeof(PGtypeHandler)); + + /* Possibly deep copy PGrecordAttDesc array. Otherwise, assign it + * to the fixed length buffer. + */ + for (i=0; i < hcnt; i++) + { + if (h[i].nattrs == 0) + continue; + + /* There are attributes but the attDescs buffer can be used rather + * than allocating and copying. The data was copied during the + * handlers memcpy prior to this loop. + */ + if (!h[i].freeAttDescs) + { + h[i].attDescs = h[i].attDescsBuf; + continue; + } + + /* ------------------------------ + * Must allocate and copy. + */ + + h[i].attDescs = (PGrecordAttDesc *) malloc( + h[i].nattrs * sizeof(PGrecordAttDesc)); + + if (!h[i].attDescs) + { + pqt_freehandlers(h, i+1); + return NULL; + } + + memcpy(h[i].attDescs, handlers[i].attDescs, + h[i].nattrs * sizeof(PGrecordAttDesc)); + } + + return h; +} + +void +pqt_cleartypes(PGtypeData *typeData) +{ + if (typeData) + { + pqt_freehandlers(typeData->typhandlers, typeData->typhcnt); + + typeData->typhandlers = NULL; + typeData->typhcnt = 0; + typeData->typhmax = 0; + } +} + +void +pqt_freehandlers(PGtypeHandler *handlers, int hcnt) +{ + int i; + + /* Free attDescs */ + for (i=0; i < hcnt; i++) + if (handlers[i].freeAttDescs && handlers[i].attDescs) + free(handlers[i].attDescs); + + if (handlers) + free(handlers); +} + +PGtypeHandler * +pqt_gethandler(PGtypeHandler *handlers, int hcnt, + const char *schema, const char *typname) +{ + int i; + int noschema = !schema || !*schema; + + if (!typname || !*typname) + return NULL; + + /* user registered types are searched first */ + for (i=0; i < hcnt; i++) + { + if ((noschema || strcmp(handlers[i].typschema, schema)==0) && + strcmp(handlers[i].typname, typname)==0) + { + return &handlers[i]; + } + } + + /* builtin types searched last */ + for (i=0; i < countof(pg_handlers); i++) + { + if ((noschema || strcmp(pg_handlers[i].typschema, schema)==0) && + strcmp(pg_handlers[i].typname, typname)==0) + { + return &pg_handlers[i]; + } + } + + return NULL; +} + +PGtypeHandler * +pqt_gethandlerbyid(PGtypeHandler *handlers, int hcnt, int id) +{ + if (id <= -1) + return NULL; + + if (id < countof(pg_handlers)) + return &pg_handlers[id]; + + id -= countof(pg_handlers); + if (id >= hcnt) + return NULL; + + return &handlers[id]; +} + +int +pqt_argssuper(PGtypeArgs *args, ...) +{ + int r; + va_list ap; + PGtypeHandler *baseclass; + PGtypeHandler *subclass = args->typhandler; + PGtypeData *resData = NULL; + + if (!args->is_put) + { + resData = (PGtypeData *) PQresultInstanceData( + args->get.result, pqt_eventproc); + + if (!resData) + return args->errorf(args, "PGresult is missing event data"); + } + + /* should always work, but play it safe */ + baseclass = pqt_gethandlerbyid( + args->is_put ? args->put.param->typhandlers : resData->typhandlers, + args->is_put ? args->put.param->typhcnt : resData->typhcnt, + subclass->base_id); + + if (!baseclass) + return args->errorf(args, "type handler has no base type"); + + args->typhandler = baseclass; + + va_copy(ap, args->ap); + va_start(args->ap, args); + r = args->is_put ? baseclass->typput(args) : baseclass->typget(args); + va_copy(args->ap, ap); + + args->typhandler = subclass; + return r; +} + +/* FQTN standards for Fully Qualified Type Name. Returns a pointer to out. + * Only returns NULL if out is NULL or outl <= 0. + */ +char * +pqt_fqtn(char *out, size_t outl, const char *schema, const char *typname) +{ + int r; + int have_schema = schema && *schema; + + if (!out || outl<=0) + return NULL; + + *out = 0; + if (!typname || !*typname) + return out; + + r = pqt_snprintf(out, outl, "%s%s%s", have_schema ? schema : "", + have_schema ? "." : "", typname); + + if (r < 0) + { + *out = 0; + return out; + } + + return out; +} + +/* only checks builin types or pqt types. User registered types must + * be checked by the user's handler functions. + */ +int +pqt_allowsptr(PGtypeHandler *h) +{ + /* pg_catalog.[bpchar, varchar, name, text, bytea] */ + if (strcmp(h->typschema, "pg_catalog")==0) + { + if (strcmp(h->typname, "bpchar") && + strcmp(h->typname, "varchar") && + strcmp(h->typname, "text") && + strcmp(h->typname, "bytea") && + strcmp(h->typname, "name")) + { + return FALSE; + } + } + /* pqt.str */ + else if (strcmp(h->typschema, "pqt")==0 && strcmp(h->typname, "str")) + { + return FALSE; + } + + return TRUE; +} + +void +pqt_getfmtinfo(const PGconn *conn, PGtypeFormatInfo *info) +{ + const char *value; + + memset(info, 0, sizeof(PGtypeFormatInfo)); + + if ((value = PQparameterStatus(conn, "DateStyle"))) + pqt_strcpy(info->datestyle, sizeof(info->datestyle), value); + + if ((value = PQparameterStatus(conn, "integer_datetimes"))) + info->integer_datetimes = strcmp(value, "on")==0 ? TRUE : FALSE; + + info->sversion = PQserverVersion(conn); + info->pversion = PQprotocolVersion(conn); +} + +static int registerSubClass(PGtypeData *connData, const char *type_name, + PGtypeProc typput, PGtypeProc typget) +{ + char *s; + PGtypeHandler *h_sub; + PGtypeHandler *h_base; + char sub_typschema[PQT_MAXIDLEN + 1]; + char sub_typname[PQT_MAXIDLEN + 1]; + char base_typschema[PQT_MAXIDLEN + 1]; + char base_typname[PQT_MAXIDLEN + 1]; + char sub_fqtn[200]; + char base_fqtn[200]; + + if (!(s = parseType(type_name, sub_typschema, sub_typname, 1))) + return FALSE; + + if (*s != '=') + { + PQseterror("Missing inheritence operator '=': %s", type_name); + return FALSE; + } + + if (!parseType(s + 1, base_typschema, base_typname, 1)) + return FALSE; + + /* lookup the base handler */ + h_base = pqt_gethandler(connData->typhandlers, connData->typhcnt, + base_typschema, base_typname); + + if (!h_base) + { + PQseterror("typname '%s' does not exist, '%s' cannot sub-class it", + pqt_fqtn(base_fqtn, sizeof(base_fqtn), base_typschema, base_typname), + pqt_fqtn(sub_fqtn, sizeof(sub_fqtn), sub_typschema, sub_typname)); + return FALSE; + } + + /* cannot sub-class a record type */ + if (h_base->typoid == RECORDOID) + { + PQseterror("Cannot sub-class pg_catalog.record '%s'", + pqt_fqtn(sub_fqtn, sizeof(sub_fqtn), sub_typschema, sub_typname)); + return FALSE; + } + + if (!expandHandlers(connData)) + return FALSE; + + h_sub = &connData->typhandlers[connData->typhcnt]; + memset(h_sub, 0, sizeof(PGtypeHandler)); + + h_sub->id = connData->typhcnt + countof(pg_handlers); + h_sub->typlen = h_base->typlen; + h_sub->typoid = h_base->typoid; + h_sub->typoid_array = h_base->typoid_array; + h_sub->typput = typput; + h_sub->typget = typget; + h_sub->base_id = h_base->id; + + pqt_strcpy(h_sub->typschema, + sizeof(h_sub->typschema), sub_typschema); + + pqt_strcpy(h_sub->typname, + sizeof(h_sub->typname), sub_typname); + + connData->typhcnt++; + return TRUE; +} + +static int +expandHandlers(PGtypeData *typeData) +{ + int hmax; + PGtypeHandler *h; + + if (typeData->typhcnt < typeData->typhmax) + return TRUE; + + hmax = typeData->typhmax ? (typeData->typhmax * 3) / 2 : 8; + h = (PGtypeHandler *) pqt_realloc( + typeData->typhandlers, sizeof(PGtypeHandler) * hmax); + + if (!h) + { + PQseterror(PQT_OUTOFMEMORY); + return FALSE; + } + + typeData->typhandlers = h; + typeData->typhmax = hmax; + return TRUE; +} + +static int +checkTypeLookups(PGresult *res, PGregisterType *types, int count) +{ + int i; + int ntups = PQntuples(res); + + /* The tuple count must match the requested count. The server omits + * tuples for types it did not find. For those it did find, it returns + * a sequenctial index. The first gap found is our missing type. This + * only reports about the first missing type. + */ + if (ntups == count) + return TRUE; + + for (i=0; i < ntups; i++) + { + int idx; + + if (!PQgetf(res, i, "%int4", 0, &idx)) + return FALSE; + + /* 'i' should always match idx-1, postgresql arrays are 1-based. + * This is a missing type, first gap in the sequence. + */ + if (i != idx-1) + break; + } + + PQseterror("server type lookup failed: could not find '%s'", + types[i].typname); + + return FALSE; +} + +/* This is part of a performance enhancement for getting arrays + * and/or composites. They require generating PGresults which + * causes pqt_duphandlers() to run. Its amazing how much a simple + * malloc+memcpy costs after around 10000 or so. The common case + * avoids this by using a fixed length PGrecordAttDesc buffer. If + * there are a large number of attributes, the slower path is used. + * Again this is small, maybe 10% of the overall 63% win. + */ +static PGrecordAttDesc *initAttDescs(PGtypeHandler *h, char *attrs) +{ + char *p; + int nattrs = 1; + PGrecordAttDesc *attDescs; + + for(p = attrs; *p; nattrs++, ++p) + if(!(p = strchr(p, ' '))) + break; + + if (nattrs < (int) (sizeof(h->attDescsBuf) / sizeof(h->attDescsBuf[0]))) + { + h->freeAttDescs = 0; + attDescs = h->attDescsBuf; + } + else + { + attDescs = (PGrecordAttDesc *) malloc(nattrs * sizeof(PGrecordAttDesc)); + if (!attDescs) + { + PQseterror(PQT_OUTOFMEMORY); + return NULL; + } + + h->freeAttDescs = 1; + } + + return attDescs; +} + +/* wraps pqt_parsetype to toggle out illegal flags during a register */ +static char *parseType(const char *spec, char *typschema, char *typname, + int typpos) +{ + char *s; + int flags; + + if (!(s = pqt_parsetype(spec, typschema, typname, &flags, typpos))) + return NULL; + + if (flags & TYPFLAG_INVALID) + return NULL; + + if (flags & TYPFLAG_ARRAY) + { + PQseterror("Cannot use an array[] during a type handler registration."); + return NULL; + } + + if (flags & TYPFLAG_POINTER) + { + PQseterror("Cannot use a type* during a type handler registration."); + return NULL; + } + + return s; +} + +static int getTypeParams(PGconn *conn, PGregisterType *types, int count, + PGarray *names, PGarray *schemas) +{ + int i; + + names->ndims = 0; + schemas->ndims = 0; + + if (!(names->param = PQparamCreate(conn))) + return FALSE; + + if (!(schemas->param = PQparamCreate(conn))) + { + PQparamClear(names->param); + return FALSE; + } + + for (i=0; i < count; i++) + { + char typname[PQT_MAXIDLEN + 1]; + char typschema[PQT_MAXIDLEN + 1]; + char *s = parseType(types[i].typname, typschema, typname, 1); + + if (!s) + { + PQparamClear(names->param); + PQparamClear(schemas->param); + return FALSE; + } + + s = *typschema ? typschema : NULL; + if (!PQputf(names->param, "%text", typname) || + !PQputf(schemas->param, "%text", s)) + { + PQparamClear(names->param); + PQparamClear(schemas->param); + return FALSE; + } + } + + return TRUE; +} + + + +/* Lookup types, including composites. Arguments are: + * schemas text[], type_names text[], want_attrs bool + */ +#define LOOKUP_TYPES \ +"WITH nspnames AS" \ +"(" \ +" SELECT * FROM information_schema._pg_expandarray(%text[])" \ +")," \ +"typnames AS" \ +"(" \ +" SELECT * FROM information_schema._pg_expandarray(%text[])" \ +")," \ +"curpath AS" \ +"(" \ +" SELECT * FROM information_schema._pg_expandarray(current_schemas(true))" \ +")," \ +"composites AS" \ +"(" \ +" SELECT n.n AS idx, n.x AS nspname, t.x AS typname" \ +" FROM nspnames n LEFT JOIN typnames t ON n.n = t.n" \ +" AND n.x IS NOT NULL" \ +" WHERE t.x IS NOT NULL" \ +" UNION ALL" \ +" SELECT n.n AS idx," \ +" (" \ +" SELECT n.nspname from pg_type nt JOIN pg_namespace n ON " \ + "nt.typnamespace = n.oid" \ +" JOIN curpath c ON c.x = n.nspname" \ +" WHERE nt.typname = t.x" \ +" ORDER BY c.n LIMIT 1" \ +" ) AS nspname, t.x AS typname" \ +" FROM nspnames n LEFT JOIN typnames t ON n.n = t.n" \ +" AND n.x IS NULL" \ +" WHERE t.x IS NOT NULL" \ +")" \ +"SELECT idx, t.oid AS typoid, a.oid AS arroid, t.typlen," \ +" (" \ +" CASE WHEN %bool THEN (" \ +" SELECT array_to_string" \ +" (" \ +" ARRAY" \ +" (" \ +" SELECT CASE WHEN tt.typtype = 'd' THEN tt.typbasetype " \ + "ELSE a.atttypid END ||" \ +" ',' || attlen || ',' || atttypmod || ',' || " \ + "encode(attname::bytea, 'hex')" \ +" FROM pg_type b" \ +" JOIN pg_attribute a ON b.typrelid = a.attrelid" \ +" JOIN pg_type tt ON a.atttypid = tt.oid" \ +" WHERE b.oid = t.oid" \ +" AND a.attnum > 0" \ +" AND NOT a.attisdropped" \ +" ORDER BY a.attnum" \ +" ), ' ')" \ +" ) ELSE NULL END) AS arr_props" \ +" FROM composites c" \ +" JOIN pg_type t ON t.typname = c.typname" \ +" JOIN pg_namespace n ON t.typnamespace = n.oid AND n.nspname = c.nspname" \ +" JOIN pg_type a ON a.oid = t.typarray" \ +" ORDER BY idx;" + +/* Lookup types for pre 8.4 servers, including composites. Arguments are: + * want_attrs bool, schemas text[], type_names text[] + */ +#define LOOKUP_TYPES_PRE_8_4 \ +"SELECT idx, t.oid AS typoid, a.oid AS arroid, t.typlen," \ +" (" \ +" CASE WHEN %bool THEN (" \ +" SELECT array_to_string" \ +" (" \ +" ARRAY" \ +" (" \ +" SELECT CASE WHEN tt.typtype = 'd' THEN tt.typbasetype " \ + "ELSE a.atttypid END ||" \ +" ',' || attlen || ',' || atttypmod || ',' || " \ + "encode(attname::bytea, 'hex')" \ +" FROM pg_type b" \ +" JOIN pg_attribute a ON b.typrelid = a.attrelid" \ +" JOIN pg_type tt ON a.atttypid = tt.oid" \ +" WHERE b.oid = t.oid" \ +" AND a.attnum > 0" \ +" AND NOT a.attisdropped" \ +" ORDER BY a.attnum" \ +" ), ' ')" \ +" ) ELSE NULL END) AS arr_props" \ +" FROM" \ +" (" \ +" SELECT n.n AS idx, n.x AS nspname, t.x AS typname" \ +" FROM" \ +" (" \ +" SELECT * FROM information_schema._pg_expandarray(%text[])" \ +" ) n LEFT JOIN" \ +" (" \ +" SELECT * FROM information_schema._pg_expandarray(%text[])" \ +" ) t ON n.n = t.n" \ +" AND n.x IS NOT NULL" \ +" WHERE t.x IS NOT NULL" \ +" UNION ALL" \ +" SELECT n.n AS idx," \ +" (" \ +" SELECT n.nspname from pg_type nt JOIN pg_namespace n ON " \ + "nt.typnamespace = n.oid" \ +" JOIN" \ +" (" \ +" SELECT * FROM information_schema._pg_expandarray(" \ + "current_schemas(true))" \ +" ) c ON c.x = n.nspname" \ +" WHERE nt.typname = t.x" \ +" ORDER BY c.n LIMIT 1" \ +" ) AS nspname, t.x AS typname" \ +" FROM" \ +" (" \ +" SELECT * FROM information_schema._pg_expandarray($2)" \ +" ) n LEFT JOIN" \ +" (" \ +" SELECT * FROM information_schema._pg_expandarray($3)" \ +" ) t ON n.n = t.n" \ +" AND n.x IS NULL" \ +" WHERE t.x IS NOT NULL" \ +" ) c" \ +" JOIN pg_type t ON t.typname = c.typname" \ +" JOIN pg_namespace n ON t.typnamespace = n.oid AND n.nspname = c.nspname" \ +" JOIN pg_type a ON a.oid = t.typarray" \ +" ORDER BY idx" + +static PGresult * +execLookupTypes(PGconn *conn, PGtypeData *data, PGarray *schemas, + PGarray *names, int want_attrs) +{ + if(data->fmtinfo.sversion >= 80400) + return PQexecf(conn, LOOKUP_TYPES, schemas, names, want_attrs); + return PQexecf(conn, LOOKUP_TYPES_PRE_8_4, want_attrs, schemas, names); +} + +static int +sendLookupTypes(PGconn *conn, PGtypeData *data, PGarray *schemas, + PGarray *names, int want_attrs) +{ + if(data->fmtinfo.sversion >= 80400) + return PQsendf(conn, LOOKUP_TYPES, schemas, names, want_attrs); + return PQsendf(conn, LOOKUP_TYPES_PRE_8_4, want_attrs, schemas, names); +} + +static int +performRegisterQuery(PGconn *conn, int which, PGregisterType *types, + int count, PGresult **res) +{ + int n = FALSE; + PGtypeData *connData; + PGarray names; + PGarray schemas; + int want_attrs = which == PQT_COMPOSITE; + + if (!(connData = (PGtypeData *) PQinstanceData(conn, pqt_eventproc))) + { + PQseterror("PGconn is missing event data"); + return FALSE; + } + + if (!getTypeParams(conn, types, count, &names, &schemas)) + return FALSE; + + if (res) + *res = execLookupTypes(conn, connData, &schemas, &names, want_attrs); + else + n = sendLookupTypes(conn, connData, &schemas, &names, want_attrs); + + PQparamClear(names.param); + PQparamClear(schemas.param); + + if (res) + return *res ? TRUE : FALSE; + + return n; +} diff --git a/source/src/libpqtypes-int.h b/source/src/libpqtypes-int.h new file mode 100644 index 0000000..00faf93 --- /dev/null +++ b/source/src/libpqtypes-int.h @@ -0,0 +1,533 @@ + +/* + * libpqtypes-int.h + * Private header file for libpqtypes. All source files include + * this header. + * + * Copyright (c) 2008-2015 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. + */ + +#ifndef LIBPQTYPES_INT_H +#define LIBPQTYPES_INT_H + +#define PLN do{ \ + printf("%s:%d\n", __FUNCTION__, __LINE__); \ + fflush(stdout); \ +}while(0) + +#ifdef HAVE_CONFIG_H +# include "pqt_config.h" +#endif + +/* Using the windows compiler */ +#ifdef _MSC_VER +# define PQT_MSVC _MSC_VER +#endif + +/* We can/should use the Windows API */ +#if defined(PQT_MSVC) || defined(__MINGW32__) +# define PQT_WINAPI +#endif + +/* Compiling on a windows platform */ +#if defined(PQT_WINAPI) || defined(__CYGWIN__) +# define PQT_WIN32 +#endif + +/* WINAPI is available, include needed winsock headers */ +#ifdef PQT_WINAPI +# ifdef PQT_MSVC +# pragma warning (disable : 4706 4100 4711 4127 4702) +# pragma warning (disable: 4706 4100 4514 4710 4201 4206) +# endif +# include +# include +# include /* need for getaddrinfo (2000 and below */ +# include +#else +# if defined(__CYGWIN__) || defined(HAVE_SYS_TYPES_H) +# include +# endif +# if defined(__CYGWIN__) || defined(HAVE_SYS_SOCKET_H) +# include +# endif +# if defined(__CYGWIN__) || defined(HAVE_NETDB_H) +# include +# endif +# if defined(__CYGWIN__) || defined(HAVE_NETINET_IN_H) +# include +# endif +# if defined(__CYGWIN__) || defined(HAVE_ARPA_INET_H) +# include +# endif +#endif + +#include +#include +#include +#include + +/* string*.h, windows platforms just include string.h */ +#ifdef PQT_WIN32 +# include +#else +# ifdef HAVE_STRING_H +# include +# endif +# ifdef HAVE_STRINGS_H +# include +# endif +#endif + +/* Include stddef.h if on windows or if we have it */ +#if defined(PQT_WIN32) || defined(HAVE_STDDEF_H) +# include +#endif + +/* Include limits.h if on windows or if we have it */ +#if defined(PQT_WIN32) || defined(HAVE_LIMITS_H) +# include +#endif + +/* Include linux/limits.h if available */ +#if defined(HAVE_CONFIG_H) && defined(HAVE_LINUX_LIMITS_H) +# include +#endif + +/* Include math.h if on windows or if we have it */ +#if defined(PQT_WIN32) || defined(HAVE_MATH_H) +# include +#endif + +/* Include the public API, pulls in libpq-fe.h for us. */ +#include "libpqtypes.h" +#include "libpq-events.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef TRUE +# define TRUE 1 +#endif + +#ifndef FALSE +# define FALSE 0 +#endif + +#ifndef NULL_LEN +# define NULL_LEN (-1) +#endif + +/* Win32 builds use _timezone and _tzname: manually define HAVE_TZNAME. */ +#ifdef PQT_WIN32 +# define HAVE_TZNAME +# define pqt_timezone _timezone +# define pqt_tzname _tzname +#else +# define pqt_timezone timezone +# define pqt_tzname tzname +#endif + +#define PQT_OUTOFMEMORY "Out of memory error" +#define PQT_MAXIDLEN 64 + +#define TEXTFMT 0 +#define BINARYFMT 1 +#define TYPFLAG_CASEFOLD 0x01 +#define TYPFLAG_ARRAY 0x02 +#define TYPFLAG_POINTER 0x04 +#define TYPFLAG_INVALID 0x08 +#define TYPFLAG_BYNAME 0x10 + +/* Avoid passing NULL to realloc, some systems don't support it */ +#define pqt_realloc(ptr, size) (ptr) ? realloc(ptr, size) : malloc(size) + +#define countof(array) (int) (sizeof(array) / sizeof(array[0])) + +/* MSVC 8 deprecated quite a few POSIX names */ +#if defined(PQT_MSVC) && PQT_MSVC >= 1400 +# undef strdup +# undef sscanf +# define strdup _strdup +# define sscanf sscanf_s +#endif + +/* define va_copy */ +#ifndef va_copy +# ifdef __va_copy +# define va_copy(dest, src) __va_copy(dest, src) +# else +# define va_copy(dest, src) (dest) = (src) +# endif +#endif + +#define pqt_hex_to_dec(v) \ + (unsigned char) (((v) > '9') ? ((v) - 'a') + 10 : (v) - '0') + +/* -------------------------------- + * Macros used by built-in handlers + */ + +/* used by get handlers */ +#define DECLLENGTH(args) \ + int valuel = PQgetlength((args)->get.result, (args)->get.tup_num, \ + (args)->get.field_num) + +/* used by get handlers */ +#define DECLVALUE(getargs) \ + char *value = PQgetvalue((getargs)->get.result, (getargs)->get.tup_num, \ + (getargs)->get.field_num) + +/* used by put handlers */ +#define PUTNULLCHK(args, valp) do{ \ + if (!(valp)) \ + return pqt_put_null(args); \ +}while (0) + +/* used by get handlers, checks for NULL pointers and PQgetisnull */ +#define CHKGETVALS(args, outp) do{ \ + if (!(outp)) \ + RERR(args, "output pointer is NULL"); \ + memset(outp, 0, sizeof(*(outp))); \ + if (PQgetisnull((args)->get.result, (args)->get.tup_num, \ + (args)->get.field_num)) \ + return 0; \ +}while (0) + +/* RERR for return error: errorf always returns -1 */ +#define RERR(_args, msg) return (_args)->errorf(_args, msg) +#define RERR_STR2INT(args) RERR(args, "String to integer conversion failed") +#define RERR_MEM(args) RERR(args, PQT_OUTOFMEMORY) + +#ifdef STRICT_MEMORY_ALIGNMENT +# define pqt_buf_putint2(_buffer, _val) do{ \ + short _v = (short) htons((short) (_val)); \ + memcpy(_buffer, &_v, sizeof(short)); \ + } while (0) + +# define pqt_buf_putint4(_buffer, _val) do{ \ + int _v = (int) htonl((int) (_val)); \ + memcpy(_buffer, &_v, sizeof(int)); \ + } while (0) + + short pqt_buf_getint2(char *buffer); + int pqt_buf_getint4(char *buffer); +#else +# define pqt_buf_putint2(_out, _val) \ + *(short *) (_out) = (short) htons((short) (_val)) +# define pqt_buf_getint2(_buffer) (short) ntohs(*(short *) (_buffer)) +# define pqt_buf_putint4(_out, _val) \ + *(int *) (_out) = (int) htonl((int) (_val)) +# define pqt_buf_getint4(_buffer) (int) ntohl(*(int *) (_buffer)) +#endif + +/* ---------------------------------- + * See: src/include/catalog/pg_type.h + * Used by built-in type handlers + */ + +/* numerics types */ +#define INT2OID 21 +#define INT4OID 23 +#define INT8OID 20 +#define FLOAT4OID 700 +#define FLOAT8OID 701 +#define NUMERICOID 1700 +/* geo types */ +#define POINTOID 600 +#define LSEGOID 601 +#define PATHOID 602 +#define BOXOID 603 +#define POLYGONOID 604 +#define LINEOID 628 /* not supported yet */ +#define CIRCLEOID 718 +/* network types */ +#define INETOID 869 +#define CIDROID 650 +#define MACADDROID 829 +/* variable length types */ +#define BPCHAROID 1042 +#define VARCHAROID 1043 +#define NAMEOID 19 +#define TEXTOID 25 +#define ZPBITOID 1560 /* not supported yet */ +#define VARBITOID 1562 /* not supported yet */ +#define BYTEAOID 17 +/* date and time types */ +#define DATEOID 1082 +#define TIMEOID 1083 +#define TIMETZOID 1266 +#define TIMESTAMPOID 1114 +#define TIMESTAMPTZOID 1184 +#define INTERVALOID 1186 +/* misc types */ +#define CHAROID 18 +#define BOOLOID 16 +#define OIDOID 26 +#define CASHOID 790 +#define RECORDOID 2249 +#define UUIDOID 2950 + +/* -------------------------------- + * Private Structures + */ + +/* Represents a param value. An array of PGvalues was choosen over + * allocating 4 separate arrays: oids, values, lengths, formats. Instead + * of 4 allocations you only have one. The PGvalue array can easily be + * converted to 4 arrays for execution. + */ +typedef struct +{ + int ptrl; /* Length of value's pointer */ + void *ptr; /* value pointer, data member uses this for non-NULL values */ + int datal; /* Length of current value: always <= ptrl */ + char *data; /* current value data, can be NULL ... thus the ptr member. */ + int format; /* format: 0=text, 1=binary */ + Oid oid; /* Oid of the data */ +} PGvalue; + +/* performance driven structure. Instead of parsing and looking up + * type specs each put or get, PQspecPrepare can be used to compile + * a spec format string into a PGtypeSpec object. The biggest wins are + * large results sets and arrays. + */ +typedef struct +{ + char *name; /* name for the prepared spec, used for lookup. */ + char *stmt; /* parameterized version of stmt, if supplied */ + int idcnt; /* number of handler ids */ + int *idlist; /* handler ids */ + unsigned char *flags; /* handler flags */ +} PGtypeSpec; + +/* PGparam structure */ +struct pg_param +{ + int vcnt; /* current number of param values */ + int vmax; /* number of PGvalue structs in 'vals' array. */ + PGvalue *vals; /* array of param values, grown when needed */ + + /* assigned during PQparamCreate */ + PGtypeFormatInfo fmtinfo; + int typhcnt; + PGtypeHandler *typhandlers; + + int typspeccnt; + PGtypeSpec *typspecs; +}; + +typedef struct +{ + PGtypeFormatInfo fmtinfo; + int typhcnt; + int typhmax; + PGtypeHandler *typhandlers; + + int typspeccnt; + int typspecmax; + PGtypeSpec *typspecs; +} PGtypeData; + +/* -------------------------------- + * Internal API functions (not exported) + */ + +/* === in events.c === */ + +/* The libpq PGEventProc */ +int pqt_eventproc(PGEventId id, void *info, void *passThrough); + +void pqt_cleartypes(PGtypeData *typeData); + +/* === in param.c === */ + +int pqt_putparam(PGparam *param, const void *data, int datal, + int flags, int format, Oid typoid); + +/* === in spec.c === */ + +PGtypeSpec *pqt_getspec(PGtypeSpec *specs, int count, const char *name); +PGtypeSpec *pqt_dupspecs(PGtypeSpec *specs, int count); +void pqt_clearspec(PGtypeSpec *cache); +void pqt_freespecs(PGtypeSpec *specs, int count); + +char *pqt_parse(const char *format, PGtypeHandler *h, int hcnt, + char *stmtBuf, size_t stmtBufLen, PGtypeHandler **out, size_t *stmtPos, + int *typpos, int *flags); + +char *pqt_parsetype(const char *spec, char *schema, char *typname, + int *flags, int typpos); + +/* === in handler.c === */ + +int pqt_allowsptr(PGtypeHandler *h); +void pqt_getfmtinfo(const PGconn *conn, PGtypeFormatInfo *info); +PGtypeHandler *pqt_duphandlers(PGtypeHandler *handlers, int hcnt); +void pqt_freehandlers(PGtypeHandler *handlers, int hcnt); +int pqt_argssuper(PGtypeArgs *args, ...); +int pqt_argserrorf(PGtypeArgs *args, const char *format, ...); +void pqt_setresultfields(const PGresult *res); + +PGtypeHandler *pqt_gethandler(PGtypeHandler *handlers, int hcnt, + const char *schema, const char *typname); + +PGtypeHandler *pqt_gethandlerbyid(PGtypeHandler *handlers, + int hcnt, int id); + +/* FQTN standards for Fully Qualified Type Name. Returns a pointer to out. + * Only returns NULL if out is NULL or outl == 0. + */ +char *pqt_fqtn(char *out, size_t outl, + const char *schema, const char *typname); + +/* == in utils.c == */ + +PGresult *pqt_copyresult(PGtypeArgs *args, int nattrs); +void pqt_swap8(void *outp, void *inp, int tonet); +int pqt_text_to_int8(char *val, void *out); +int pqt_text_to_float8(double *f8, char *text, char **endptr); + +/* Checks buffer and truncates 'src' if 'dest' is too small. */ +char *pqt_strcpy(char *dest, size_t size, const char *src); + +/* Taken from postgres project (named pg_xxx) */ +unsigned char pqt_tolower(unsigned char ch); +int pqt_strcasecmp(const char *s1, const char *s2); +int pqt_strncasecmp(const char *s1, const char *s2, size_t n); + +/* == in port.c == */ + +/* Define a strtoll macro for windows, except for MinGW & Cygwin + * which always have it. MSVC has _strtoi64, but MSVC 6 and under + * require that we declare the prototype. + */ +#ifdef PQT_MSVC +# if PQT_MSVC <= 1200 + __int64 __cdecl _strtoi64(const char *, char **, int); +# endif +# define strtoll _strtoi64 +#endif + +/* In rare cases these could be absent (older platforms). We implement + * drop-in replacements in port.c for those cases. strtod handled by + * configure, which adds a replacement for us. + * + * NOTE: only unixes use configure so sections wrapped around + * HAVE_CONFIG_H exclude windows. + */ +#ifdef HAVE_CONFIG_H +# ifndef HAVE_STRTOL + long strtol(const char *nptr, char **endptr, int base); +# endif +# ifndef HAVE_STRTOUL + unsigned long strtoul(const char *nptr, char **endptr, int base); +# endif +# ifndef HAVE_STRTOLL + long long int strtoll(const char *nptr, char **endptr, int base); +# endif +#endif + +/* Handles platform differences and always returns -1 for failure. + * On windows: MSVC 8 uses _snprintf_s, otherwise _snprintf. + */ +int pqt_snprintf(char *buf, size_t size, const char *format, ...); +int pqt_vsnprintf(char *buf, size_t size, const char *format, va_list ap); + +/* -------------------------------- + * Built-in Type Handlers + */ + +/* === in array.c === */ + +int pqt_put_array(PGtypeArgs *args); +int pqt_get_array(PGtypeArgs *args); + +/* == in datetime.c == */ + +int pqt_put_date(PGtypeArgs *args); +int pqt_get_date(PGtypeArgs *args); +int pqt_put_time(PGtypeArgs *args); +int pqt_get_time(PGtypeArgs *args); +int pqt_put_timetz(PGtypeArgs *args); +int pqt_get_timetz(PGtypeArgs *args); +int pqt_put_timestamp(PGtypeArgs *args); +int pqt_get_timestamp(PGtypeArgs *args); +int pqt_put_timestamptz(PGtypeArgs *args); +int pqt_get_timestamptz(PGtypeArgs *args); +int pqt_put_interval(PGtypeArgs *args); +int pqt_get_interval(PGtypeArgs *args); + +/* == in geo.c == */ + +int pqt_put_point(PGtypeArgs *args); +int pqt_get_point(PGtypeArgs *args); +int pqt_put_lseg(PGtypeArgs *args); +int pqt_get_lseg(PGtypeArgs *args); +int pqt_put_box(PGtypeArgs *args); +int pqt_get_box(PGtypeArgs *args); +int pqt_put_circle(PGtypeArgs *args); +int pqt_get_circle(PGtypeArgs *args); +int pqt_put_path(PGtypeArgs *args); +int pqt_get_path(PGtypeArgs *args); +int pqt_put_polygon(PGtypeArgs *args); +int pqt_get_polygon(PGtypeArgs *args); + +/* == in misc.c == */ + +int pqt_put_char(PGtypeArgs *args); /* "char" type, not char(N) */ +int pqt_get_char(PGtypeArgs *args); +int pqt_put_bool(PGtypeArgs *args); +int pqt_get_bool(PGtypeArgs *args); +int pqt_put_money(PGtypeArgs *args); +int pqt_get_money(PGtypeArgs *args); +int pqt_put_uuid(PGtypeArgs *args); +int pqt_get_uuid(PGtypeArgs *args); +int pqt_put_null(PGtypeArgs *args); /* no get for null */ + +/* == in network.c == */ + +int pqt_put_inet(PGtypeArgs *args); /* handles cidr */ +int pqt_get_inet(PGtypeArgs *args); +int pqt_get_cidr(PGtypeArgs *args); +int pqt_put_macaddr(PGtypeArgs *args); +int pqt_get_macaddr(PGtypeArgs *args); + +/* == in numerics.c == */ + +int pqt_put_int2(PGtypeArgs *args); +int pqt_get_int2(PGtypeArgs *args); +int pqt_put_int4(PGtypeArgs *args); /* handles oid */ +int pqt_get_int4(PGtypeArgs *args); /* handles oid */ +int pqt_put_int8(PGtypeArgs *args); +int pqt_get_int8(PGtypeArgs *args); +int pqt_put_float4(PGtypeArgs *args); +int pqt_get_float4(PGtypeArgs *args); +int pqt_put_float8(PGtypeArgs *args); +int pqt_get_float8(PGtypeArgs *args); +int pqt_put_numeric(PGtypeArgs *args); +int pqt_get_numeric(PGtypeArgs *args); + +/* == record.c (composites) == */ + +int pqt_put_record(PGtypeArgs *args); +int pqt_get_record(PGtypeArgs *args); + +/* == in varlena.c == */ + +int pqt_put_str(PGtypeArgs *args); /* no get for str - use PQgetvalue */ +int pqt_put_text(PGtypeArgs *args); /* handles varchar, bpchar and name */ +int pqt_get_text(PGtypeArgs *args); /* handles varchar, bpchar and name */ +int pqt_put_bytea(PGtypeArgs *args); +int pqt_get_bytea(PGtypeArgs *args); + +#ifdef __cplusplus +} +#endif +#endif + + diff --git a/source/src/libpqtypes.h b/source/src/libpqtypes.h new file mode 100644 index 0000000..6e25a7d --- /dev/null +++ b/source/src/libpqtypes.h @@ -0,0 +1,468 @@ + +/* + * libpqtypes.h + * Public header for libpqtypes. Contains the entire public API. + * + * Copyright (c) 2008-2015 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. + */ + +#ifndef LIBPQTYPES_H +#define LIBPQTYPES_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__CYGWIN__) +# define PQT_EXPORT __declspec(dllexport) +#else +# define PQT_EXPORT extern +#endif + +/* MSVC 6 must use `i64', everything else uses `LL'. */ +#if !defined(_MSC_VER) || _MSC_VER > 1200 +# define PQT_INT64CONST(x) ((PGint8) x##LL) +#else +# define PQT_INT64CONST(x) ((PGint8) x##i64) +#endif + +enum +{ + PQT_SUBCLASS, + PQT_COMPOSITE, + PQT_USERDEFINED +}; + +typedef struct pg_param PGparam; +typedef struct pg_typeargs PGtypeArgs; +typedef int (*PGtypeProc)(PGtypeArgs *args); + +/* For use with a PQregisterXXX function */ +typedef struct +{ + const char *typname; + PGtypeProc typput; + PGtypeProc typget; +} PGregisterType; + +typedef struct +{ + int sversion; + int pversion; + char datestyle[48]; + int integer_datetimes; +} PGtypeFormatInfo; + +/* Record Attribute Description, its columns */ +typedef struct +{ + Oid attoid; + int attlen; + int atttypmod; + char attname[65]; +} PGrecordAttDesc; + +/* Type handler for putf and getf functions. The char fixed length buffers + * used to be allocated pointers. This was a performance problem when + * many type handlers are registered and one uses getf on a composite or + * an array. These types require generating a PGresult and duplicating + * the type handlers. Saved 40% by not having to deep copy the strings. + */ +typedef struct pg_typhandler +{ + int id; + char typschema[65]; + char typname[65]; + int typlen; + Oid typoid; + Oid typoid_array; + PGtypeProc typput; + PGtypeProc typget; + int base_id; + + /* For composites, contains each attribute of a composite */ + int nattrs; + int freeAttDescs; + PGrecordAttDesc attDescsBuf[16]; + PGrecordAttDesc *attDescs; +} PGtypeHandler; + +/* Values required during a type handler put ot get operation. */ +struct pg_typeargs +{ + int is_put; + const PGtypeFormatInfo *fmtinfo; + int is_ptr; + int format; + va_list ap; + int typpos; + PGtypeHandler *typhandler; + int (*errorf)(PGtypeArgs *args, const char *format, ...); + int (*super)(PGtypeArgs *args, ...); + + struct + { + PGparam *param; + char *out; + char *__allocated_out; /* leave me alone! */ + int outl; + int (*expandBuffer)(PGtypeArgs *args, int new_len); + } put; + + struct + { + PGresult *result; + int tup_num; + int field_num; + } get; +}; + + +/* ---------------- + * Variable Length types + * ---------------- + */ + +typedef char *PGtext; +typedef char *PGvarchar; +typedef char *PGbpchar; +typedef char *PGuuid; +typedef struct +{ + int len; + char *data; +} PGbytea; + +/* ---------------- + * Numeric types + * ---------------- + */ + +typedef signed char PGchar; +typedef int PGbool; +typedef short PGint2; +typedef int PGint4; +typedef float PGfloat4; +typedef double PGfloat8; +typedef char *PGnumeric; + +/* Defined by an end-user if the system is missing long long. */ +#ifdef PQT_LONG_LONG + typedef PQT_LONG_LONG PGint8; + typedef PQT_LONG_LONG PGmoney; + +/* MinGW and MSVC can use __int64 */ +#elif defined(__MINGW32__) || defined(_MSC_VER) + typedef __int64 PGint8; + typedef __int64 PGmoney; + +/* Cygwin and Unixes. */ +#else + typedef long long PGint8; + typedef long long PGmoney; +#endif + +/* ---------------- + * Geometric type structures + * ---------------- + */ + +typedef struct +{ + double x; + double y; +} PGpoint; + +typedef struct +{ + PGpoint pts[2]; +} PGlseg; + +typedef struct +{ + PGpoint high; + PGpoint low; +} PGbox; + +typedef struct +{ + PGpoint center; + double radius; +} PGcircle; + +typedef struct +{ + int npts; + int closed; + PGpoint *pts; /* for getf, only valid while PGresult is. */ +} PGpath; + +typedef struct +{ + int npts; + PGpoint *pts; /* for getf, only valid while PGresult is. */ +} PGpolygon; + +/* ---------------- + * Network type structures + * ---------------- + */ + +/* This struct works with CIDR as well. */ +typedef struct +{ + int mask; + int is_cidr; + int sa_buf_len; + + /* sockaddr buffer, can be casted to sockaddr, sockaddr_in, + * sockaddr_in6 or sockaddr_stroage. + */ + char sa_buf[128]; +} PGinet; + +typedef struct +{ + int a; + int b; + int c; + int d; + int e; + int f; +} PGmacaddr; + +/* ---------------- + * Date & Time structures + * ---------------- + */ + +typedef struct +{ + int years; + int mons; + int days; + int hours; + int mins; + int secs; + int usecs; +} PGinterval; + +typedef struct +{ + int isbc; + int year; + int mon; + int mday; + int jday; + int yday; + int wday; +} PGdate; + +typedef struct +{ + int hour; + int min; + int sec; + int usec; + int withtz; + int isdst; + int gmtoff; + char tzabbr[16]; +} PGtime; + +typedef struct +{ + PGint8 epoch; + PGdate date; + PGtime time; +} PGtimestamp; + +/* ---------------- + * Array structures + * ---------------- + */ + +#ifndef MAXDIM +# define MAXDIM 6 +#endif + +typedef struct +{ + int ndims; + int lbound[MAXDIM]; + int dims[MAXDIM]; + PGparam *param; + PGresult *res; +} PGarray; + +/* ---------------- + * Public API funcs + * ---------------- + */ + +/* === in events.c === */ + +/* Deprecated, see PQinitTypes */ +PQT_EXPORT int +PQtypesRegister(PGconn *conn); + +/* === in error.c === */ + +PQT_EXPORT char * +PQgeterror(void); + +/* PQseterror(NULL) will clear the error message */ +PQT_EXPORT void +PQseterror(const char *format, ...); + +/* Gets the error field for the last executed query. This only + * pertains to PQparamExec and PQparamExecPrepared. When using a + * standard libpq function like PQexec, PQresultErrorField should be used. + */ +PQT_EXPORT char * +PQgetErrorField(int fieldcode); + +/* === in spec.c === */ + +/* Set 'format' argument to NULL to clear a single prepared specifier. */ +PQT_EXPORT int +PQspecPrepare(PGconn *conn, const char *name, const char *format, int is_stmt); + +PQT_EXPORT int +PQclearSpecs(PGconn *conn); + +/* === in handler.c === */ + +/* Initialize type support on the given connection */ +PQT_EXPORT int +PQinitTypes(PGconn *conn); + +/* Deprecated, see PQregisterTypes */ +PQT_EXPORT int +PQregisterSubClasses(PGconn *conn, PGregisterType *types, int count); + +/* Deprecated, see PQregisterTypes */ +PQT_EXPORT int +PQregisterComposites(PGconn *conn, PGregisterType *types, int count); + +/* Deprecated, see PQregisterTypes */ +PQT_EXPORT int +PQregisterUserDefinedTypes(PGconn *conn, PGregisterType *types, int count); + +/* Registers PQT_SUBCLASS, PQT_COMPOSITE or PQT_USERDEFINED + * (the 'which' argument) for use with libpqtypes. + * + * For asynchronous type registration, set the 'async' argument to a + * non-zero value. This value is ignored when 'which' is PQT_SUBCLASS, + * since subclass registration does not execute any commands against the + * server. Use the standard PQconsumeInput, PQisBusy and PQgetResult + * to properly obtain a PGresult, which must be passed to PQregisterResult + * to complete the registration. + */ +PQT_EXPORT int +PQregisterTypes(PGconn *conn, int which, PGregisterType *types, + int count, int async); + +/* Registers a set of 'which' types found in the given PGresult. Caller + * is responsible for clearing the result 'res'. Useful for performing + * asynchronous type registration or for caching type result data to + * avoid lookups on a new connection. If PQregisterTypes is ran in async + * mode, the PGresult obtained via PGgetResult can be cached by an + * application and provided to this function for new connections. + * + * Types and count should be identical to what was originally supplied + * to PQregisterTypes. + * + * NOTE: although a PGconn is a required argument, it is never used + * to perform any network operation (non-blocking safe). + * + * PQT_SUBCLASS is not supported and will result in an error if supplied. + */ +PQT_EXPORT int +PQregisterResult(PGconn *conn, int which, PGregisterType *types, + int count, PGresult *res); + +/* Clears all type handlers registered on 'conn'. This is useful after a + * PQreset or PQresetPoll to optionally allow one to re-register types via + * PQregisterTypes. + */ +PQT_EXPORT int +PQclearTypes(PGconn *conn); + +/* === in param.c === */ + +PQT_EXPORT PGparam * +PQparamCreate(const PGconn *conn); + +PQT_EXPORT PGparam * +PQparamDup(PGparam *param); + +PQT_EXPORT int +PQparamCount(PGparam *param); + +PQT_EXPORT void +PQparamReset(PGparam *param); + +PQT_EXPORT void +PQparamClear(PGparam *param); + +PQT_EXPORT int +PQputf(PGparam *param, const char *format, ...); + +PQT_EXPORT int +PQputvf(PGparam *param, char *stmtBuf, size_t stmtBufLen, + const char *format, va_list ap); + +/* === in exec.c === */ + +PQT_EXPORT int +PQgetf(const PGresult *res, int tup_num, const char *format, ...); + +PQT_EXPORT int +PQgetvf(const PGresult *res, int tup_num, const char *format, va_list ap); + +PQT_EXPORT PGresult * +PQexecf(PGconn *conn, const char *cmdspec, ...); + +PQT_EXPORT PGresult * +PQexecvf(PGconn *conn, const char *cmdspec, va_list ap); + +PQT_EXPORT int +PQsendf(PGconn *conn, const char *cmdspec, ...); + +PQT_EXPORT int +PQsendvf(PGconn *conn, const char *cmdspec, va_list ap); + +PQT_EXPORT PGresult * +PQparamExec(PGconn *conn, PGparam *param, + const char *command, int resultFormat); + +PQT_EXPORT int +PQparamSendQuery(PGconn *conn, PGparam *param, + const char *command, int resultFormat); + +PQT_EXPORT PGresult * +PQparamExecPrepared(PGconn *conn, PGparam *param, + const char *stmtName, int resultFormat); + +PQT_EXPORT int +PQparamSendQueryPrepared(PGconn *conn, PGparam *param, + const char *stmtName, int resultFormat); + +/* === in datetime.c === */ + +PQT_EXPORT void +PQlocalTZInfo(time_t *t, int *gmtoff, int *isdst, char **tzabbrp); + +#ifdef __cplusplus +} +#endif +#endif /* !LIBPQTYPES_H */ + diff --git a/source/src/misc.c b/source/src/misc.c new file mode 100644 index 0000000..d6d04fc --- /dev/null +++ b/source/src/misc.c @@ -0,0 +1,199 @@ + +/* + * misc.c + * Type handler for CHAR, BOOL, MONEY, UUID data types. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +int +pqt_put_char(PGtypeArgs *args) +{ + PGchar c; + + if (args->is_ptr) + { + PGchar *ptr = (PGchar *) va_arg(args->ap, PGchar *); + PUTNULLCHK(args, ptr); + c = *ptr; + } + else + { + c = (PGchar) va_arg(args->ap, int); + } + + *args->put.out = c; + return 1; +} + +int +pqt_get_char(PGtypeArgs *args) +{ + DECLVALUE(args); + PGchar *chp = va_arg(args->ap, PGchar *); + CHKGETVALS(args, chp); + *chp = *value; + return 0; +} + +int +pqt_put_bool(PGtypeArgs *args) +{ + char b; + + if (args->is_ptr) + { + PGbool *ptr = (PGbool *) va_arg(args->ap, PGbool *); + PUTNULLCHK(args, ptr); + b = (char) *ptr; + } + else + { + b = (char) va_arg(args->ap, PGbool); + } + + *args->put.out = b != 0 ? 1 : 0; + return 1; +} + +int +pqt_get_bool(PGtypeArgs *args) +{ + DECLVALUE(args); + PGbool *boolp = va_arg(args->ap, PGbool *); + + CHKGETVALS(args, boolp); + + if (args->format == TEXTFMT) + *boolp = (*value == 't') ? 1 : 0; + else + *boolp = (*value)!=0 ? 1 : 0; + return 0; +} + +int +pqt_put_money(PGtypeArgs *args) +{ + PGmoney money; + int len = args->fmtinfo->sversion >= 80300 ? 8 : 4; + + if (args->is_ptr) + { + PGmoney *ptr = va_arg(args->ap, PGmoney *); + PUTNULLCHK(args, ptr); + money = *ptr; + } + else + { + money = va_arg(args->ap, PGmoney); + } + + if (len == 8) + pqt_swap8(args->put.out, &money, 1); + else /* truncate: pre 8.3 server expecting a 4 byte money */ + pqt_buf_putint4(args->put.out, (int) money); + + return len; +} + +int +pqt_get_money(PGtypeArgs *args) +{ + DECLVALUE(args); + DECLLENGTH(args); + PGmoney *moneyp = va_arg(args->ap, PGmoney *); + + CHKGETVALS(args, moneyp); + + if (args->format == TEXTFMT) + { + char buf[64]; + char c, *p = buf; + char *bufend = buf + sizeof(buf); + + for (; (c = *value) != '\0' && pap, PGuuid); + PUTNULLCHK(args, uuid); + args->put.out = uuid; + return 16; +} + +int +pqt_get_uuid(PGtypeArgs *args) +{ + DECLVALUE(args); + PGuuid *uuid = va_arg(args->ap, PGuuid *); + + CHKGETVALS(args, uuid); + + if (args->format == TEXTFMT) + { + int i; + char buf[128]; + char *p = buf; + DECLLENGTH(args); + + /* format: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 */ + for (i=0; i < valuel; i++) + { + if (value[i] != '-') + { + *p++ = (pqt_hex_to_dec(value[i]) << 4) | pqt_hex_to_dec(value[i+1]); + i++; + } + } + + *uuid = (char *) PQresultAlloc(args->get.result, p - buf); + if (!*uuid) + RERR_MEM(args); + + memcpy(*uuid, buf, p - buf); + return 0; + } + + *uuid = value; + return 0; +} + +int +pqt_put_null(PGtypeArgs *args) +{ + args->put.out = NULL; + return 0; +} + diff --git a/source/src/network.c b/source/src/network.c new file mode 100644 index 0000000..1fec3f3 --- /dev/null +++ b/source/src/network.c @@ -0,0 +1,257 @@ + +/* + * network.c + * Type handler for the network data types. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +#if defined(__CYGWIN__) || (defined(HAVE_CONFIG_H) && \ + !defined(HAVE_GETADDRINFO)) +# include "getaddrinfo.h" +#endif + +#ifndef PGSQL_AF_INET +# define PGSQL_AF_INET (AF_INET + 0) +#endif + +#ifndef PGSQL_AF_INET6 +# define PGSQL_AF_INET6 (AF_INET + 1) +#endif + +#ifndef AF_INET6 +#warning NO AF_INET6 SUPPORT! +#endif + +/* Some platforms don't define this, like AIX 4.3 */ +#ifndef AI_NUMERICHOST +# define AI_NUMERICHOST 0x04 +#endif + +/* handles cidr as well */ +int +pqt_put_inet(PGtypeArgs *args) +{ + unsigned char *b = (unsigned char *)args->put.out; + PGinet *inet = va_arg(args->ap, PGinet *); + int family; + + PUTNULLCHK(args, inet); + + family = ((struct sockaddr *)inet->sa_buf)->sa_family; + + if (family == AF_INET) + { + struct sockaddr_in *sa = (struct sockaddr_in *) inet->sa_buf; + *b++ = (unsigned char) PGSQL_AF_INET; + *b++ = (unsigned char) inet->mask; + *b++ = (unsigned char) (inet->is_cidr ? 1 : 0); + *b++ = (unsigned char) 4; + memcpy(b, &sa->sin_addr, 4); + b += 4; + } +#ifdef AF_INET6 + else if (family == AF_INET6) + { + struct sockaddr_in6 *sa = (struct sockaddr_in6 *) inet->sa_buf; + *b++ = (unsigned char) PGSQL_AF_INET6; + *b++ = (unsigned char) inet->mask; + *b++ = (unsigned char) (inet->is_cidr ? 1 : 0); + *b++ = (unsigned char) 16; + memcpy(b, &sa->sin6_addr, 16); + b += 16; + } +#endif + else + { + return args->errorf(args, "Unknown inet address family %d", family); + } + + return (int) ((char *) b - args->put.out); +} + +static int +get_inet2(PGtypeArgs *args, int is_cidr) +{ + DECLVALUE(args); + unsigned short family; + PGinet *inet = va_arg(args->ap, PGinet *); + + CHKGETVALS(args, inet); + + if (args->format == TEXTFMT) + { + int r; + char *p; + char ipstr[80]; + struct addrinfo *ai = NULL; + struct addrinfo hints; + + pqt_strcpy(ipstr, sizeof(ipstr), value); + if ((p = strrchr(ipstr, '/'))) + { + *p = 0; + inet->mask = atoi(p+1); + } + else + { + inet->mask = 32; + } + + inet->is_cidr = is_cidr; + + /* suppress hostname lookups */ + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + + /* Without this, windows chokes with WSAHOST_NOT_FOUND */ +#ifdef PQT_WIN32 + hints.ai_family = AF_INET; +#endif + + if ((r = getaddrinfo(ipstr, NULL, &hints, &ai)) || !ai) + { + if(r == EAI_BADFLAGS) + { + hints.ai_flags = 0; + r = getaddrinfo(ipstr, NULL, &hints, &ai); + } + /* Another WSAHOST_NOT_FOUND work around, but for IPv6 */ +#if defined(PQT_WIN32) && defined(AF_INET6) + else if(r == WSAHOST_NOT_FOUND) + { + hints.ai_flags = 0; + hints.ai_family = AF_INET6; + r = getaddrinfo(ipstr, NULL, &hints, &ai); + } +#endif + + if(r) + RERR_STR2INT(args); + } + + inet->sa_buf_len = (int) ai->ai_addrlen; + memcpy(inet->sa_buf, ai->ai_addr, inet->sa_buf_len); + + /* Some platforms, lika AIX 4.3, do not zero this structure properly. + * The family and port are dirty, so set the first 4 bytes to 0 and + * then re-set the family. I saw "0x1002" as the first 2 bytes of + * this structure (dumb getaddrinfo), it should be "0x0002" for AF_INET. + */ + memset(inet->sa_buf, 0, 4); + ((struct sockaddr *)inet->sa_buf)->sa_family = ai->ai_addr->sa_family; + + /* Uninitialized memory, postgres inet/cidr types don't store this. + * Make sure its set to 0. Another AIX problem (maybe other platforms). + */ +#ifdef AF_INET6 + if (ai->ai_addr->sa_family == AF_INET6) + ((struct sockaddr_in6 *)inet->sa_buf)->sin6_flowinfo = 0; +#endif + + freeaddrinfo(ai); + return 0; + } + + family = (unsigned short) *value++; + if (family == PGSQL_AF_INET) + { + struct sockaddr_in *sa = (struct sockaddr_in *) inet->sa_buf; + sa->sin_family = AF_INET; + inet->mask = (unsigned char) *value++; + inet->is_cidr = *value++; + memcpy(&sa->sin_addr, value + 1, *value); + inet->sa_buf_len = (int) sizeof(struct sockaddr_in); + } +#ifdef AF_INET6 + else if (family == PGSQL_AF_INET6) + { + struct sockaddr_in6 *sa = (struct sockaddr_in6 *) inet->sa_buf; + sa->sin6_family = AF_INET6; + inet->mask = (unsigned char) *value++; + inet->is_cidr = *value++; + memcpy(&sa->sin6_addr, value + 1, *value); + inet->sa_buf_len = (int) sizeof(struct sockaddr_in6); + } +#endif + else + { + return args->errorf(args, "Unknown inet address family %d", family); + } + + return 0; +} + +int +pqt_get_inet(PGtypeArgs *args) +{ + return get_inet2(args, 0); +} + +int +pqt_get_cidr(PGtypeArgs *args) +{ + return get_inet2(args, 1); +} + +int +pqt_put_macaddr(PGtypeArgs *args) +{ + PGmacaddr *mac = va_arg(args->ap, PGmacaddr *); + + PUTNULLCHK(args, mac); + + args->put.out[0] = (unsigned char) mac->a; + args->put.out[1] = (unsigned char) mac->b; + args->put.out[2] = (unsigned char) mac->c; + args->put.out[3] = (unsigned char) mac->d; + args->put.out[4] = (unsigned char) mac->e; + args->put.out[5] = (unsigned char) mac->f; + return 6; +} + +int +pqt_get_macaddr(PGtypeArgs *args) +{ + DECLVALUE(args); + unsigned char *p; + PGmacaddr *mac = va_arg(args->ap, PGmacaddr *); + + CHKGETVALS(args, mac); + + if (args->format == TEXTFMT) + { + int a,b,c,d,e,f; + int count = sscanf(value, "%x:%x:%x:%x:%x:%x", &a,&b,&c,&d,&e,&f); + + if (count != 6 || (a < 0) || (a > 255) || (b < 0) || (b > 255) || + (c < 0) || (c > 255) || (d < 0) || (d > 255) || + (e < 0) || (e > 255) || (f < 0) || (f > 255)) + RERR_STR2INT(args); + + mac->a = a; + mac->b = b; + mac->c = c; + mac->d = d; + mac->e = e; + mac->f = f; + return 0; + } + + p = (unsigned char *) value; + mac->a = *p++; + mac->b = *p++; + mac->c = *p++; + mac->d = *p++; + mac->e = *p++; + mac->f = *p; + return 0; +} + + + diff --git a/source/src/numerics.c b/source/src/numerics.c new file mode 100644 index 0000000..66da3a0 --- /dev/null +++ b/source/src/numerics.c @@ -0,0 +1,838 @@ + +/* + * numeric.c + * Type handler for the numeric data types. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" +#include +#include + +/* Extremely annoying warning "conversion from 'int ' to 'short'" on + * Visual Studio 6. Normally this warning is useful but not in this + * case. Disable it. + */ +#if defined(PQT_MSVC) && PQT_MSVC <= 1200 +# pragma warning (disable : 4244) +#endif + +/* + * Macros and structures for receiving numeric field in binary + */ +#define NBASE 10000 +#define HALF_NBASE 5000 +#define DEC_DIGITS 4 /* decimal digits per NBASE digit */ +#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */ +#define DIV_GUARD_DIGITS 4 + +/* + * Hardcoded precision limit - arbitrary, but must be small enough that + * dscale values will fit in 14 bits. + */ +#define NUMERIC_MAX_PRECISION 1000 + +/* + * Sign values and macros to deal with packing/unpacking n_sign_dscale + */ +#define NUMERIC_SIGN_MASK 0xC000 +#define NUMERIC_POS 0x0000 +#define NUMERIC_NEG 0x4000 +#define NUMERIC_NAN 0xC000 +#define NUMERIC_DSCALE_MASK 0x3FFF +#define NUMERIC_SIGN(n) ((n)->n_sign_dscale & NUMERIC_SIGN_MASK) +#define NUMERIC_DSCALE(n) ((n)->n_sign_dscale & NUMERIC_DSCALE_MASK) +#define NUMERIC_IS_NAN(n) (NUMERIC_SIGN(n) != NUMERIC_POS && \ + NUMERIC_SIGN(n) != NUMERIC_NEG) + +typedef short NumericDigit; +static const int round_powers[4] = {0, 1000, 100, 10}; + +typedef struct NumericVar +{ + int ndigits; /* # of digits in digits[] - can be 0! */ + int weight; /* weight of first digit */ + int sign; /* NUMERIC_POS, NUMERIC_NEG, or NUMERIC_NAN */ + int dscale; /* display scale */ + NumericDigit *buf; /* start of palloc'd space for digits[] */ + NumericDigit *digits; /* base-NBASE digits */ +} NumericVar; + +static NumericVar const_nan = +{0, 0, NUMERIC_NAN, 0, NULL, NULL}; + +static int str2num(PGtypeArgs *args, const char *str, NumericVar *dest); +static int num2str(char *out, size_t outl, NumericVar *var, int dscale); + +int +pqt_put_int2(PGtypeArgs *args) +{ + PGint2 n; + + if (args->is_ptr) + { + PGint4 *nptr = va_arg(args->ap, PGint4 *); + PUTNULLCHK(args, nptr); + n = (PGint2) *nptr; + } + else + { + n = (PGint2) va_arg(args->ap, PGint4); + } + + pqt_buf_putint2(args->put.out, n); + return 2; +} + +int +pqt_get_int2(PGtypeArgs *args) +{ + DECLVALUE(args); + PGint2 *i2p = va_arg(args->ap, PGint2 *); + + CHKGETVALS(args, i2p); + + if (args->format == TEXTFMT) + { + int n; + + errno = 0; + if ((n = (int) strtol(value, NULL, 10)) == 0 && errno) + RERR_STR2INT(args); + + *i2p = (PGint2)n; + return 0; + } + + *i2p = pqt_buf_getint2(value); + return 0; +} + +/* handles oid as well */ +int +pqt_put_int4(PGtypeArgs *args) +{ + PGint4 n; + + if (args->is_ptr) + { + PGint4 *nptr = va_arg(args->ap, PGint4 *); + PUTNULLCHK(args, nptr); + n = *nptr; + } + else + { + n = (PGint4) va_arg(args->ap, PGint4); + } + + *(PGint4 *)args->put.out = (PGint4) htonl(n); + return 4; +} + +/* handles oid as well */ +int +pqt_get_int4(PGtypeArgs *args) +{ + DECLVALUE(args); + PGint4 *i4p = va_arg(args->ap, PGint4 *); + + CHKGETVALS(args, i4p); + + if (args->format == TEXTFMT) + { + PGint4 n; + + /* Use strtoul so this can support Oid */ + if ((n = (PGint4) strtoul(value, NULL, 10)) == 0 && errno) + RERR_STR2INT(args); + + *i4p = n; + return 0; + } + + *i4p = (PGint4) pqt_buf_getint4(value); + return 0; +} + +int +pqt_put_int8(PGtypeArgs *args) +{ + PGint8 i8; + + if (args->is_ptr) + { + PGint8 *nptr = va_arg(args->ap, PGint8 *); + PUTNULLCHK(args, nptr); + i8 = *nptr; + } + else + { + i8 = va_arg(args->ap, PGint8); + } + + pqt_swap8(args->put.out, &i8, 1); + return 8; +} + +int +pqt_get_int8(PGtypeArgs *args) +{ + DECLVALUE(args); + PGint8 *i8p = va_arg(args->ap, PGint8 *); + + CHKGETVALS(args, i8p); + + if (args->format == TEXTFMT) + { + if (pqt_text_to_int8(value, i8p) == -1) + RERR_STR2INT(args); + return 0; + } + + pqt_swap8(i8p, value, 0); + return 0; +} + +int +pqt_put_float4(PGtypeArgs *args) +{ + void *vp; + PGfloat4 f; + + if (args->is_ptr) + { + vp = (void *) va_arg(args->ap, double *); + PUTNULLCHK(args, vp); + } + else + { + f = (PGfloat4) va_arg(args->ap, double); + vp = (void *)&f; + } + + pqt_buf_putint4(args->put.out, *(int *) vp); + return 4; +} + +int +pqt_get_float4(PGtypeArgs *args) +{ + DECLVALUE(args); + int *f4p = (int *) va_arg(args->ap, PGfloat4 *); + + CHKGETVALS(args, f4p); + + if (args->format == TEXTFMT) + { + PGfloat4 f; + + errno = 0; + if ((f = (PGfloat4) strtod(value, NULL)) == 0 && errno) + RERR_STR2INT(args); + + *(PGfloat4 *) f4p = f; + return 0; + } + + *f4p = pqt_buf_getint4(value); + return 0; +} + +int +pqt_put_float8(PGtypeArgs *args) +{ + PGfloat8 d; + + if (args->is_ptr) + { + PGfloat8 *ptr = (PGfloat8 *) va_arg(args->ap, PGfloat8 *); + PUTNULLCHK(args, ptr); + d = *ptr; + } + else + { + d = va_arg(args->ap, PGfloat8); + } + + pqt_swap8(args->put.out, &d, 1); + return 8; +} + +int +pqt_get_float8(PGtypeArgs *args) +{ + DECLVALUE(args); + PGfloat8 *f8p = va_arg(args->ap, PGfloat8 *); + + CHKGETVALS(args, f8p); + + if (args->format == TEXTFMT) + { + if (!pqt_text_to_float8(f8p, value, NULL)) + RERR_STR2INT(args); + + return 0; + } + + pqt_swap8(f8p, value, 0); + return 0; +} + +/* exposing a NumericVar struct to a libpq user, or something similar, + * doesn't seem useful w/o a library to operate on it. Instead, we + * always expose a numeric in text format and let the API user decide + * how to use it .. like strod or a 3rd party big number library. We + * always send a numeric in binary though. + */ +int +pqt_put_numeric(PGtypeArgs *args) +{ + int numlen; + NumericVar num = {0}; + short *out; + PGnumeric str = va_arg(args->ap, PGnumeric); + + PUTNULLCHK(args, str); + + if (str2num(args, str, &num)) + { + if (num.digits) + free(num.digits); + return -1; + } + + /* variable length data type, grow args->put.out buffer if needed */ + numlen = (int) sizeof(short) * (4 + num.ndigits); + if (args->put.expandBuffer(args, numlen) == -1) + return -1; + + out = (short *) args->put.out; + *out++ = htons((short) num.ndigits); + *out++ = htons((short) num.weight); + *out++ = htons((short) num.sign); + *out++ = htons((short) num.dscale); + + if (num.digits) + { + int i; + for (i=0; i < num.ndigits; i++) + *out++ = htons(num.digits[i]); + free(num.digits); + } + + return numlen; +} + +/* exposing a NumericVar struct to a libpq user, or something similar, + * doesn't seem useful w/o a library to operate on it. Instead, we + * always expose a numeric in text format and let the API user decide + * how to use it .. like strod or a 3rd party big number library. + */ +int +pqt_get_numeric(PGtypeArgs *args) +{ + int i; + short *s; + NumericVar num; + DECLVALUE(args); + char buf[4096]; + size_t len; + PGnumeric *str = va_arg(args->ap, PGnumeric *); + + CHKGETVALS(args, str); + + if (args->format == TEXTFMT) + { + *str = value; + return 0; + } + + s = (short *) value; + num.ndigits = ntohs(*s); s++; + num.weight = (short) ntohs(*s); s++; + num.sign = ntohs(*s); s++; + num.dscale = ntohs(*s); s++; + num.digits = (short *) malloc(num.ndigits * sizeof(short)); + if (!num.digits) + RERR_MEM(args); + + for (i=0; i < num.ndigits; i++) + { + num.digits[i] = ntohs(*s); + s++; + } + + i = num2str(buf, sizeof(buf), &num, num.dscale); + free(num.digits); + + /* num2str failed, only fails when 'str' is too small */ + if (i == -1) + RERR(args, "out buffer is too small"); + + len = strlen(buf)+1; + *str = PQresultAlloc(args->get.result, len); + if (!*str) + RERR_MEM(args); + + memcpy(*str, buf, len); + return 0; + +} + + +/* + * round_var + * + * Round the value of a variable to no more than rscale decimal digits + * after the decimal point. NOTE: we allow rscale < 0 here, implying + * rounding before the decimal point. + */ +static void +round_var(NumericVar *var, int rscale) +{ + NumericDigit *digits = var->digits; + int di; + int ndigits; + int carry; + + var->dscale = rscale; + + /* decimal digits wanted */ + di = (var->weight + 1) * DEC_DIGITS + rscale; + + /* + * If di = 0, the value loses all digits, but could round up to 1 if its + * first extra digit is >= 5. If di < 0 the result must be 0. + */ + if (di < 0) + { + var->ndigits = 0; + var->weight = 0; + var->sign = NUMERIC_POS; + } + else + { + /* NBASE digits wanted */ + ndigits = (di + DEC_DIGITS - 1) / DEC_DIGITS; + + /* 0, or number of decimal digits to keep in last NBASE digit */ + di %= DEC_DIGITS; + + if (ndigits < var->ndigits || + (ndigits == var->ndigits && di > 0)) + { + var->ndigits = ndigits; + + if (di == 0) + carry = (digits[ndigits] >= HALF_NBASE) ? 1 : 0; + else + { + /* Must round within last NBASE digit */ + int extra, + pow10; + + pow10 = round_powers[di]; + extra = digits[--ndigits] % pow10; + digits[ndigits] = digits[ndigits] - (NumericDigit) extra; + carry = 0; + if (extra >= pow10 / 2) + { + pow10 += digits[ndigits]; + if (pow10 >= NBASE) + { + pow10 -= NBASE; + carry = 1; + } + digits[ndigits] = (NumericDigit) pow10; + } + } + + /* Propagate carry if needed */ + while (carry) + { + carry += digits[--ndigits]; + if (carry >= NBASE) + { + digits[ndigits] = (NumericDigit) (carry - NBASE); + carry = 1; + } + else + { + digits[ndigits] = (NumericDigit) carry; + carry = 0; + } + } + + if (ndigits < 0) + { + var->digits--; + var->ndigits++; + var->weight++; + } + } + } +} + +/* + * strip_var + * + * Strip any leading and trailing zeroes from a numeric variable + */ +static void +strip_var(NumericVar *var) +{ + NumericDigit *digits = var->digits; + int ndigits = var->ndigits; + + /* Strip leading zeroes */ + while (ndigits > 0 && *digits == 0) + { + digits++; + var->weight--; + ndigits--; + } + + /* Strip trailing zeroes */ + while (ndigits > 0 && digits[ndigits - 1] == 0) + ndigits--; + + /* If it's zero, normalize the sign and weight */ + if (ndigits == 0) + { + var->sign = NUMERIC_POS; + var->weight = 0; + } + + if (ndigits != var->ndigits) { + memcpy(var->digits, digits, (ndigits) * sizeof(NumericDigit)); + var->ndigits = ndigits; + } +} + +/* + * str2num() + * + * Parse a string and put the number into a variable + * returns -1 on error and 0 for success. + */ +static int +str2num(PGtypeArgs *args, const char *str, NumericVar *dest) +{ + const char *cp = str; + int have_dp = FALSE; + int i; + unsigned char *decdigits; + int sign = NUMERIC_POS; + int dweight = -1; + int ddigits; + int dscale = 0; + int weight; + int ndigits; + int offset; + NumericDigit *digits; + + /* + * We first parse the string to extract decimal digits and determine the + * correct decimal weight. Then convert to NBASE representation. + */ + + /* skip leading spaces */ + while (*cp) + { + if (!isspace((unsigned char) *cp)) + break; + cp++; + } + + /* + * Check for NaN + */ + if (tolower(*cp) == 'n' && tolower(*(cp+1)) == 'a' + && tolower(*(cp+2)) == 'n') + { + cp += 3; + /* Should be nothing left but spaces */ + while (*cp) + { + if (!isspace(*cp)) + { + return args->errorf(args, + "invalid input syntax for type numeric: '%s'", str); + } + cp++; + } + *dest = const_nan; + return 0; + } + + switch (*cp) + { + case '+': + sign = NUMERIC_POS; + cp++; + break; + + case '-': + sign = NUMERIC_NEG; + cp++; + break; + } + + if (*cp == '.') + { + have_dp = TRUE; + cp++; + } + + if (!isdigit((unsigned char) *cp)) + return args->errorf(args, + "invalid input syntax for type numeric: '%s'", str); + + decdigits = (unsigned char *) malloc(strlen(cp) + DEC_DIGITS * 2); + + /* leading padding for digit alignment later */ + memset(decdigits, 0, DEC_DIGITS); + i = DEC_DIGITS; + + while (*cp) + { + if (isdigit((unsigned char) *cp)) + { + decdigits[i++] = *cp++ - '0'; + if (!have_dp) + dweight++; + else + dscale++; + } + else if (*cp == '.') + { + if (have_dp) + { + free(decdigits); + return args->errorf(args, + "invalid input syntax for type numeric: '%s'", str); + } + + have_dp = TRUE; + cp++; + } + else + break; + } + + ddigits = i - DEC_DIGITS; + /* trailing padding for digit alignment later */ + memset(decdigits + i, 0, DEC_DIGITS - 1); + + /* Handle exponent, if any */ + if (*cp == 'e' || *cp == 'E') + { + long exponent; + char *endptr; + + cp++; + exponent = strtol(cp, &endptr, 10); + if (endptr == cp) + { + free(decdigits); + return args->errorf(args, + "invalid input syntax for type numeric: '%s'", str); + } + + cp = endptr; + if (exponent > NUMERIC_MAX_PRECISION || + exponent < -NUMERIC_MAX_PRECISION) + { + free(decdigits); + return args->errorf(args, + "invalid input syntax for type numeric: '%s'", str); + } + + dweight += (int) exponent; + dscale -= (int) exponent; + if (dscale < 0) + dscale = 0; + } + + /* Should be nothing left but spaces */ + while (*cp) + { + if (!isspace((unsigned char) *cp)) + { + free(decdigits); + return args->errorf(args, + "invalid input syntax for type numeric: '%s'", str); + } + cp++; + } + + /* + * Okay, convert pure-decimal representation to base NBASE. First we need + * to determine the converted weight and ndigits. offset is the number of + * decimal zeroes to insert before the first given digit to have a + * correctly aligned first NBASE digit. + */ + if (dweight >= 0) + weight = (dweight + 1 + DEC_DIGITS - 1) / DEC_DIGITS - 1; + else + weight = -((-dweight - 1) / DEC_DIGITS + 1); + offset = (weight + 1) * DEC_DIGITS - (dweight + 1); + ndigits = (ddigits + offset + DEC_DIGITS - 1) / DEC_DIGITS; + + dest->digits = (NumericDigit *) malloc((ndigits) * sizeof(NumericDigit)); + dest->ndigits = ndigits; + dest->sign = sign; + dest->weight = weight; + dest->dscale = dscale; + + i = DEC_DIGITS - offset; + digits = dest->digits; + + while (ndigits-- > 0) + { + *digits++ = ((decdigits[i] * 10 + decdigits[i + 1]) * 10 + + decdigits[i + 2]) * 10 + decdigits[i + 3]; + i += DEC_DIGITS; + } + + free(decdigits); + + /* Strip any leading/trailing zeroes, and normalize weight if zero */ + strip_var(dest); + return 0; +} + +/* + * num2str() - + * + * Convert a var to text representation (guts of numeric_out). + * CAUTION: var's contents may be modified by rounding! + * returns -1 on error and 0 for success. + */ +static int +num2str(char *out, size_t outl, NumericVar *var, int dscale) +{ + //char *str; + char *cp; + char *endcp; + int i; + int d; + NumericDigit dig; + NumericDigit d1; + + /* + * Handle NaN + */ + if (var->sign == NUMERIC_NAN) { + strcpy(out, "NaN"); + return 0; + } + + if (dscale < 0) + dscale = 0; + + /* + * Check if we must round up before printing the value and do so. + */ + round_var(var, dscale); + + /* + * Allocate space for the result. + * + * i is set to to # of decimal digits before decimal point. dscale is the + * # of decimal digits we will print after decimal point. We may generate + * as many as DEC_DIGITS-1 excess digits at the end, and in addition we + * need room for sign, decimal point, null terminator. + */ + i = (var->weight + 1) * DEC_DIGITS; + if (i <= 0) + i = 1; + + if (outl <= (size_t) (i + dscale + DEC_DIGITS + 2)) + return -1; + + //str = palloc(i + dscale + DEC_DIGITS + 2); + //cp = str + cp = out; + + /* + * Output a dash for negative values + */ + if (var->sign == NUMERIC_NEG) + *cp++ = '-'; + + /* + * Output all digits before the decimal point + */ + if (var->weight < 0) + { + d = var->weight + 1; + *cp++ = '0'; + } + else + { + for (d = 0; d <= var->weight; d++) + { + dig = (d < var->ndigits) ? var->digits[d] : 0; + /* In the first digit, suppress extra leading decimal zeroes */ + { + int putit = (d > 0); + + d1 = dig / 1000; + dig -= d1 * 1000; + putit |= (d1 > 0); + if (putit) + *cp++ = (char) (d1 + '0'); + d1 = dig / 100; + dig -= d1 * 100; + putit |= (d1 > 0); + if (putit) + *cp++ = (char) (d1 + '0'); + d1 = dig / 10; + dig -= d1 * 10; + putit |= (d1 > 0); + if (putit) + *cp++ = (char) (d1 + '0'); + *cp++ = (char) (dig + '0'); + } + } + } + + /* + * If requested, output a decimal point and all the digits that follow it. + * We initially put out a multiple of DEC_DIGITS digits, then truncate if + * needed. + */ + if (dscale > 0) + { + *cp++ = '.'; + endcp = cp + dscale; + for (i = 0; i < dscale; d++, i += DEC_DIGITS) + { + dig = (d >= 0 && d < var->ndigits) ? var->digits[d] : 0; + d1 = dig / 1000; + dig -= d1 * 1000; + *cp++ = (char) (d1 + '0'); + d1 = dig / 100; + dig -= d1 * 100; + *cp++ = (char) (d1 + '0'); + d1 = dig / 10; + dig -= d1 * 10; + *cp++ = (char) (d1 + '0'); + *cp++ = (char) (dig + '0'); + } + cp = endcp; + } + + /* + * terminate the string and return it + */ + *cp = '\0'; + return 0; +} + + diff --git a/source/src/param.c b/source/src/param.c new file mode 100644 index 0000000..78aa6af --- /dev/null +++ b/source/src/param.c @@ -0,0 +1,493 @@ + +/* + * param.c + * Management functions for the PGparam object. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +static int argsExpandBuffer(PGtypeArgs *args, int new_len); + +PGparam * +PQparamCreate(const PGconn *conn) +{ + PGparam *param; + PGtypeData *connData; + + PQseterror(NULL); + + if (!conn) + { + PQseterror("PGconn cannot be NULL"); + return NULL; + } + + param = (PGparam *) malloc(sizeof(PGparam)); + if (!param) + { + PQseterror(PQT_OUTOFMEMORY); + return NULL; + } + + memset(param, 0, sizeof(PGparam)); + + connData = (PGtypeData *) PQinstanceData(conn, pqt_eventproc); + if (!connData) + { + PQparamClear(param); + PQseterror("No type data exists for PGconn at %p", conn); + return NULL; + } + + /* copy any handlers from conn object */ + if (connData->typhcnt > 0) + { + param->typhandlers = pqt_duphandlers( + connData->typhandlers, connData->typhcnt); + + if (!param->typhandlers) + { + PQparamClear(param); + PQseterror(PQT_OUTOFMEMORY); + return NULL; + } + + param->typhcnt = connData->typhcnt; + } + + /* copy any typespecs from conn object */ + if (connData->typspeccnt > 0) + { + param->typspecs = pqt_dupspecs( + connData->typspecs, connData->typspeccnt); + + if (!param->typspecs) + { + PQparamClear(param); + PQseterror(PQT_OUTOFMEMORY); + return NULL; + } + + param->typspeccnt = connData->typspeccnt; + } + + pqt_getfmtinfo(conn, ¶m->fmtinfo); + return param; +} + +PQT_EXPORT PGparam * +PQparamDup(PGparam *param) +{ + PGparam *new_param; + + PQseterror(NULL); + + if (!param) + { + PQseterror("PGparam to duplicate cannot be NULL"); + return NULL; + } + + new_param = (PGparam *) malloc(sizeof(PGparam)); + if (!new_param) + { + PQseterror(PQT_OUTOFMEMORY); + return NULL; + } + + memset(new_param, 0, sizeof(PGparam)); + + /* copy any handlers from conn object */ + if (param->typhcnt > 0) + { + new_param->typhandlers = pqt_duphandlers( + param->typhandlers, param->typhcnt); + + if (!new_param->typhandlers) + { + PQparamClear(new_param); + PQseterror(PQT_OUTOFMEMORY); + return NULL; + } + + new_param->typhcnt = param->typhcnt; + } + + /* copy any typespecs from conn object */ + if (param->typspeccnt > 0) + { + new_param->typspecs = pqt_dupspecs( + param->typspecs, param->typspeccnt); + + if (!new_param->typspecs) + { + PQparamClear(new_param); + PQseterror(PQT_OUTOFMEMORY); + return NULL; + } + + new_param->typspeccnt = param->typspeccnt; + } + + memcpy(&new_param->fmtinfo, ¶m->fmtinfo, sizeof(PGtypeFormatInfo)); + + /* copy any values, don't bother if array is empty. */ + if (param->vcnt > 0) + { + int i; + + for (i=0; i < param->vcnt; i++) + { + int flags = 0; + PGvalue *val = ¶m->vals[i]; + + /* Is val->data is direct user pointer? If so, set pointer flag + * so pqt_putparam doesn't deep copy it. + */ + if (val->ptr != val->data) + flags |= TYPFLAG_POINTER; + + if (!pqt_putparam(new_param, val->data, val->datal, flags, + val->format, val->oid)) + { + PQparamClear(new_param); + return NULL; + } + } + } + + return new_param; +} + +int +PQparamCount(PGparam *param) +{ + if (param) + return param->vcnt; + return 0; +} + +void +PQparamReset(PGparam *param) +{ + if (param) + param->vcnt = 0; +} + +void +PQparamClear(PGparam *param) +{ + int i; + + if (!param) + return; + + /* vmax is the size of the vals array. We don't use vcnt because + * someone could of just called PQparamReset, leaving vcnt 0. If + * we find any value pointers that are not NULL, free'm. + */ + for (i=0; i < param->vmax; i++) + if (param->vals[i].ptr) + free(param->vals[i].ptr); + + if (param->vals) + free(param->vals); + + pqt_freehandlers(param->typhandlers, param->typhcnt); + pqt_freespecs(param->typspecs, param->typspeccnt); + + param->vmax = 0; + param->vcnt = 0; + param->vals = NULL; + param->typhcnt = 0; + param->typhandlers = NULL; + param->typspeccnt = 0; + param->typspecs = NULL; + + free(param); +} + +int +PQputf(PGparam *param, const char *format, ...) +{ + int r; + va_list ap; + + /* This function is just a wrapper to PQputvf */ + va_start(ap, format); + r = PQputvf(param, NULL, 0, format, ap); + va_end(ap); + + return r; +} + +int +PQputvf(PGparam *param, char *stmtBuf, size_t stmtBufLen, + const char *format, va_list ap) +{ + int n=0; + int flags; + size_t stmtPos = 0; + int save_vcnt; + int typpos = 0; + PGtypeHandler *h; + PGtypeArgs args; + char args_outbuf[4096]; + PGtypeSpec *spec = NULL; + + PQseterror(NULL); + + if (!param) + { + PQseterror("PGparam cannot be NULL"); + return FALSE; + } + + if (!format || !*format) + { + PQseterror("param 'format' cannot be NULL or an empty string"); + return FALSE; + } + + if (stmtBuf && stmtBufLen < 1) + { + PQseterror("Invalid argument: stmtBufLen must be >= 1"); + return FALSE; + } + + save_vcnt = param->vcnt; + va_copy(args.ap, ap); + + /* "@name" format, lookup typeSpec in cache */ + if(format && *format == '@') + { + + spec = pqt_getspec(param->typspecs, param->typspeccnt, format + 1); + + /* If we didn't find a type spec, this is an error. A format string + * with a '@' as its first character is reserved. + */ + if (!spec) + { + PQseterror("No such prepared specifier name: '%s'", format + 1); + return FALSE; + } + } + + while (format && *format) + { + if (spec) + { + /* done, no more handlers in cached spec string. */ + if (typpos == spec->idcnt) + break; + + h = pqt_gethandlerbyid(param->typhandlers, param->typhcnt, + spec->idlist[typpos]); + + /* should be an unusual, or a "will never happen", situation */ + if (!h) + { + va_end(args.ap); + PQseterror("Unknown type handler id at position %d", typpos+1); + param->vcnt = save_vcnt; + return FALSE; + } + + flags = (int) spec->flags[typpos]; + typpos++; + } + else + { + format = pqt_parse(format, param->typhandlers, param->typhcnt, + stmtBuf, stmtBufLen, &h, &stmtPos, &typpos, &flags); + + if (!format) + { + param->vcnt = save_vcnt; + return FALSE; + } + + if (!h) + continue; + } + + args.is_put = 1; + args.put.param = param; + args.fmtinfo = ¶m->fmtinfo; + args.put.out = args_outbuf; + args.put.__allocated_out = NULL; + args.put.outl = (int) sizeof(args_outbuf); + args.is_ptr = (flags & TYPFLAG_POINTER) ? 1 : 0; + args.format = BINARYFMT; + args.put.expandBuffer = argsExpandBuffer; + args.typpos = typpos; + args.typhandler = h; + args.errorf = pqt_argserrorf; + args.super = pqt_argssuper; + *args.put.out = 0; + + if (flags & TYPFLAG_ARRAY) + n = pqt_put_array(&args); + else + n = h->typput(&args); + + if (n == -1) + { + if (args.put.__allocated_out && args.put.__allocated_out != args_outbuf) + free(args.put.__allocated_out); + param->vcnt = save_vcnt; + return FALSE; + } + + if (args.put.out == NULL) + { + args.format = BINARYFMT; + n = -1; + } + + n = pqt_putparam(param, args.put.out, n, flags, args.format, + (flags & TYPFLAG_ARRAY) ? h->typoid_array : h->typoid); + + if (args.put.__allocated_out && args.put.__allocated_out != args_outbuf) + free(args.put.__allocated_out); + + if (!n) + { + param->vcnt = save_vcnt; + return FALSE; + } + } + + if (stmtBuf) + stmtBuf[stmtPos] = 0; + + return TRUE; +} + + +/* ---------------------------- + * Helper functions + */ + +int +pqt_putparam(PGparam *param, const void *data, int datal, + int flags, int format, Oid typoid) +{ + PGvalue *v; + + if (!param) + return FALSE; + + if (!data) + datal = -1; + + /* need to grow param vals array */ + if (param->vcnt == param->vmax) + { + PGvalue *vals; + int vmax = param->vmax ? (param->vmax * 3) / 2 : 16; + + vals = (PGvalue *) pqt_realloc(param->vals, sizeof(PGvalue) * vmax); + if (!vals) + { + PQseterror(PQT_OUTOFMEMORY); + return FALSE; + } + + /* zero out the new array elements */ + memset(vals + param->vcnt, 0, (vmax - param->vcnt) * sizeof(PGvalue)); + param->vmax = vmax; + param->vals = vals; + } + + /* grab next param value */ + v = ¶m->vals[param->vcnt]; + + if (datal == -1) + { + v->data = NULL; + } + else if (datal == 0) + { + /* BUG FIX: allows putting empty strings in binary mode. Without this, + * empty strings would be converted to an SQL NULL. + * Reported by: Dmitry Epstein + */ + static char zero[1] = {0}; + v->data = zero; + } + /* wants to put a direct pointer */ + else if (flags & TYPFLAG_POINTER) + { + v->data = (char *) data; + } + else + { + /* need more mem for param value ptr */ + if (v->ptrl < datal) + { + char *ptr = (char *) pqt_realloc(v->ptr, datal); + + if (!ptr) + { + PQseterror(PQT_OUTOFMEMORY); + return FALSE; + } + + v->ptrl = datal; + v->ptr = ptr; + } + + memcpy(v->ptr, data, datal); + v->data = v->ptr; + } + + v->datal = datal; + v->format = format; + v->oid = typoid; + param->vcnt++; + return TRUE; +} + +/* returns 0 on success and -1 on error */ +static int +argsExpandBuffer(PGtypeArgs *args, int new_len) +{ + char *new_out; + + if (new_len <= args->put.outl) + return 0; + + if (!args->put.__allocated_out) + { + new_out = (char *) malloc(new_len); + if (!new_out) + return args->errorf(args, PQT_OUTOFMEMORY); + + /* fully copy existing stack buffer, we don't know what data + * the user has already written and may want to keep. + */ + memcpy(new_out, args->put.out, args->put.outl); + } + else + { + new_out = (char *) realloc(args->put.__allocated_out, new_len); + if (!new_out) + return args->errorf(args, PQT_OUTOFMEMORY); + } + + args->put.out = args->put.__allocated_out = new_out; + args->put.outl = new_len; + return 0; +} + + + + + diff --git a/source/src/port.c b/source/src/port.c new file mode 100644 index 0000000..c11a80e --- /dev/null +++ b/source/src/port.c @@ -0,0 +1,587 @@ + +/* + * port.c + * Portability functions. Some of these functions are drop-ins for + * systems missing them and others just centralize differences. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +#if defined(__MINGW32__) || defined(__CYGWIN__) +# define HAVE_VSNPRINTF +#endif + +int +pqt_snprintf(char *buf, size_t size, const char *format, ...) +{ + int n; + va_list ap; + va_start(ap, format); + n = pqt_vsnprintf(buf, size, format, ap); + va_end(ap); + return n; +} + +int +pqt_vsnprintf(char *buf, size_t size, const char *format, va_list ap) +{ + int n; + +#ifdef PQT_MSVC +# if PQT_MSVC >= 1400 + /* MSVC 8 */ + n = _vsnprintf_s(buf, size, size-1, format, ap); +# else + /* MSVC 6 or 7 */ + n = _vsnprintf(buf, size, format, ap); +# endif + +#elif defined(HAVE_VSNPRINTF) + /* All other platforms, including MinGW and Cygwin. */ + n = vsnprintf(buf, size, format, ap); +#else + /* Some platforms don't have the buffer-safe version */ + n = vsprintf(buf, format, ap); +#endif + + if (n > -1 && (size_t) n < size) + return n; + + /* Although some implementations return "required" buf size, this + * always return -1 to keep things consistent for caller. + */ + return -1; +} + +#if defined(HAVE_CONFIG_H) && (!defined(HAVE_STRTOL) || \ + !defined(HAVE_STRTOUL)) + +static unsigned long string2long(const char *nptr, char **endptr, + int base, int is_signed); + +#ifndef HAVE_STRTOL +long +strtol(const char *nptr, char **endptr, int base) +{ + return (signed long) string2long(nptr, endptr, base, 1); +} +#endif + +#ifndef HAVE_STRTOUL +unsigned long +strtoul(const char *nptr, char **endptr, int base) +{ + return (unsigned long) string2long(nptr, endptr, base, 0); +} +#endif + +#define between(a, c, z) \ + ((unsigned) ((c) - (a)) <= (unsigned) ((z) - (a))) + +static unsigned long +string2long(const char *nptr, char ** const endptr, + int base, int is_signed) +{ + unsigned int v; + unsigned long val = 0; + int c; + int ovfl = 0, sign = 1; + const char *startnptr = nptr, *nrstart; + + if (endptr) + *endptr = (char *) nptr; + + while (isspace(*nptr)) + nptr++; + c = *nptr; + + if (c == '-' || c == '+') + { + if (c == '-') + sign = -1; + nptr++; + } + nrstart = nptr; /* start of the number */ + + /* When base is 0, the syntax determines the actual base */ + if (base == 0) + { + if (*nptr == '0') + { + if (*++nptr == 'x' || *nptr == 'X') + { + base = 16; + nptr++; + } + else + { + base = 8; + } + } + else + { + base = 10; + } + } + else if (base==16 && *nptr=='0' && (*++nptr =='x' || *nptr =='X')) + { + nptr++; + } + + for (;;) + { + c = *nptr; + if (between('0', c, '9')) + v = c - '0'; + else if (between('a', c, 'z')) + v = c - 'a' + 0xa; + else if (between('A', c, 'Z')) + v = c - 'A' + 0xA; + else + break; + + if (v >= base) + break; + if (val > (ULONG_MAX - v) / base) + ovfl++; + + val = (val * base) + v; + nptr++; + } + + if (endptr) + { + if (nrstart == nptr) + *endptr = (char *) startnptr; + else + *endptr = (char *) nptr; + } + + if (!ovfl) + { + /* Overflow is only possible when converting a signed long. */ + if (is_signed && ((sign < 0 && val > -(unsigned long) LONG_MIN) + || (sign > 0 && val > LONG_MAX))) + ovfl++; + } + + if (ovfl) + { + errno = ERANGE; + if (is_signed) + { + if (sign < 0) + return LONG_MIN; + else + return LONG_MAX; + } + else + { + return ULONG_MAX; + } + } + + return (long) sign * val; +} + +#endif /* !strtol || !strtoul */ + + +/* Non-windows platforms that don't have strtoll */ +#if defined(HAVE_CONFIG_H) && !defined(HAVE_STRTOLL) + +#define MIN_INT64 (-MAX_INT64 - PQT_INT64CONST(1)) +#define MAX_INT64 PQT_INT64CONST(9223372036854775807) + +/* no locale support */ +long long int +strtoll(const char *nptr, char **endptr, int base) +{ + const char *s; + /* LONGLONG */ + long long int acc, cutoff; + int c; + int neg, any, cutlim; + + /* endptr may be NULL */ + +#ifdef __GNUC__ + /* This outrageous construct just to shut up a GCC warning. */ + (void) &acc; (void) &cutoff; +#endif + + /* + * Skip white space and pick up leading +/- sign if any. + * If base is 0, allow 0x for hex and 0 for octal, else + * assume decimal; if base is already 16, allow 0x. + */ + s = nptr; + do { + c = (unsigned char) *s++; + } while (isspace(c)); + if (c == '-') { + neg = 1; + c = *s++; + } else { + neg = 0; + if (c == '+') + c = *s++; + } + if ((base == 0 || base == 16) && + c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + + /* + * Compute the cutoff value between legal numbers and illegal + * numbers. That is the largest legal value, divided by the + * base. An input number that is greater than this value, if + * followed by a legal input character, is too big. One that + * is equal to this value may be valid or not; the limit + * between valid and invalid numbers is then based on the last + * digit. For instance, if the range for long longs is + * [-9223372036854775808..9223372036854775807] and the input base + * is 10, cutoff will be set to 922337203685477580 and cutlim to + * either 7 (neg==0) or 8 (neg==1), meaning that if we have + * accumulated a value > 922337203685477580, or equal but the + * next digit is > 7 (or 8), the number is too big, and we will + * return a range error. + * + * Set any if any `digits' consumed; make it negative to indicate + * overflow. + */ + cutoff = neg ? MIN_INT64 : MAX_INT64; + cutlim = (int) (cutoff % base); + cutoff /= base; + if (neg) { + if (cutlim > 0) { + cutlim -= base; + cutoff += 1; + } + cutlim = -cutlim; + } + for (acc = 0, any = 0;; c = (unsigned char) *s++) { + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0) + continue; + if (neg) { + if (acc < cutoff || (acc == cutoff && c > cutlim)) { + any = -1; + acc = MIN_INT64; + errno = ERANGE; + } else { + any = 1; + acc *= base; + acc -= c; + } + } else { + if (acc > cutoff || (acc == cutoff && c > cutlim)) { + any = -1; + acc = MAX_INT64; + errno = ERANGE; + } else { + any = 1; + acc *= base; + acc += c; + } + } + } + if (endptr != 0) + /* LINTED interface specification */ + *endptr = (char *) (any ? s - 1 : nptr); + return (acc); +} + +#endif /* !HAVE_STRTOLL */ + + +/* Non-windows machines missing getaddrinfo (postgres's port) */ +#if defined(__CYGWIN__) || (defined(HAVE_CONFIG_H) && \ + !defined(HAVE_GETADDRINFO)) +#undef FRONTEND +#include +#include +#include +#include + +#include "getaddrinfo.h" + +extern int h_errno; + +/* + * get address info for ipv4 sockets. + * + * Bugs: - only one addrinfo is set even though hintp is NULL or + * ai_socktype is 0 + * - AI_CANONNAME is not supported. + * - servname can only be a number, not text. + */ +int +getaddrinfo(const char *node, const char *service, + const struct addrinfo * hintp, + struct addrinfo ** res) +{ + struct addrinfo *ai; + struct sockaddr_in sin, + *psin; + struct addrinfo hints; + + if (hintp == NULL) + { + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + } + else + memcpy(&hints, hintp, sizeof(hints)); + + if (hints.ai_family != AF_INET && hints.ai_family != AF_UNSPEC) + return EAI_FAMILY; + + if (hints.ai_socktype == 0) + hints.ai_socktype = SOCK_STREAM; + + if (!node && !service) + return EAI_NONAME; + + memset(&sin, 0, sizeof(sin)); + + sin.sin_family = AF_INET; + + if (node) + { + if (node[0] == '\0') + sin.sin_addr.s_addr = htonl(INADDR_ANY); + else if (hints.ai_flags & AI_NUMERICHOST) + { + if (!inet_aton(node, &sin.sin_addr)) + return EAI_FAIL; + } + else + { + struct hostent *hp; + +#ifdef FRONTEND + struct hostent hpstr; + char buf[BUFSIZ]; + int herrno = 0; + + pqGethostbyname(node, &hpstr, buf, sizeof(buf), + &hp, &herrno); +#else + hp = gethostbyname(node); +#endif + if (hp == NULL) + { + switch (h_errno) + { + case HOST_NOT_FOUND: + case NO_DATA: + return EAI_NONAME; + case TRY_AGAIN: + return EAI_AGAIN; + case NO_RECOVERY: + default: + return EAI_FAIL; + } + } + if (hp->h_addrtype != AF_INET) + return EAI_FAIL; + + memcpy(&(sin.sin_addr), hp->h_addr, hp->h_length); + } + } + else + { + if (hints.ai_flags & AI_PASSIVE) + sin.sin_addr.s_addr = htonl(INADDR_ANY); + else + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } + + if (service) + sin.sin_port = htons((unsigned short) atoi(service)); + +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + sin.sin_len = sizeof(sin); +#endif + + ai = malloc(sizeof(*ai)); + if (!ai) + return EAI_MEMORY; + + psin = malloc(sizeof(*psin)); + if (!psin) + { + free(ai); + return EAI_MEMORY; + } + + memcpy(psin, &sin, sizeof(*psin)); + + ai->ai_flags = 0; + ai->ai_family = AF_INET; + ai->ai_socktype = hints.ai_socktype; + ai->ai_protocol = hints.ai_protocol; + ai->ai_addrlen = sizeof(*psin); + ai->ai_addr = (struct sockaddr *) psin; + ai->ai_canonname = NULL; + ai->ai_next = NULL; + + *res = ai; + + return 0; +} + + +void +freeaddrinfo(struct addrinfo * res) +{ + if (res) + { + if (res->ai_addr) + free(res->ai_addr); + free(res); + } +} + + +const char * +gai_strerror(int errcode) +{ +#ifdef HAVE_HSTRERROR + int hcode; + + switch (errcode) + { + case EAI_NONAME: + hcode = HOST_NOT_FOUND; + break; + case EAI_AGAIN: + hcode = TRY_AGAIN; + break; + case EAI_FAIL: + default: + hcode = NO_RECOVERY; + break; + } + + return hstrerror(hcode); +#else /* !HAVE_HSTRERROR */ + + switch (errcode) + { + case EAI_NONAME: + return "Unknown host"; + case EAI_AGAIN: + return "Host name lookup failure"; + /* Errors below are probably WIN32 only */ +#ifdef EAI_BADFLAGS + case EAI_BADFLAGS: + return "Invalid argument"; +#endif +#ifdef EAI_FAMILY + case EAI_FAMILY: + return "Address family not supported"; +#endif +#ifdef EAI_MEMORY + case EAI_MEMORY: + return "Not enough memory"; +#endif +#ifdef EAI_NODATA +#ifndef WIN32_ONLY_COMPILER /* MSVC complains because another case has the + * same value */ + case EAI_NODATA: + return "No host data of that type was found"; +#endif +#endif +#ifdef EAI_SERVICE + case EAI_SERVICE: + return "Class type not found"; +#endif +#ifdef EAI_SOCKTYPE + case EAI_SOCKTYPE: + return "Socket type not supported"; +#endif + default: + return "Unknown server error"; + } +#endif /* HAVE_HSTRERROR */ +} + +/* + * Convert an ipv4 address to a hostname. + * + * Bugs: - Only supports NI_NUMERICHOST and NI_NUMERICSERV + * It will never resolv a hostname. + * - No IPv6 support. + */ +int +getnameinfo(const struct sockaddr * sa, int salen, + char *node, int nodelen, + char *service, int servicelen, int flags) +{ + /* Invalid arguments. */ + if (sa == NULL || (node == NULL && service == NULL)) + return EAI_FAIL; + + /* We don't support those. */ + if ((node && !(flags & NI_NUMERICHOST)) + || (service && !(flags & NI_NUMERICSERV))) + return EAI_FAIL; + +#if defined(HAVE_IPV6) || defined(AF_INET6) + if (sa->sa_family == AF_INET6) + return EAI_FAMILY; +#endif + + if (node) + { + int ret = -1; + + if (sa->sa_family == AF_INET) + { + char *p; + + p = inet_ntoa(((struct sockaddr_in *) sa)->sin_addr); + ret = pqt_snprintf(node, nodelen, "%s", p); + } + if (ret == -1) + return EAI_MEMORY; + } + + if (service) + { + int ret = -1; + + if (sa->sa_family == AF_INET) + { + ret = pqt_snprintf(service, servicelen, "%d", + ntohs(((struct sockaddr_in *) sa)->sin_port)); + } + if (ret == -1) + return EAI_MEMORY; + } + + return 0; +} + +#endif /* HAVE_GETADDRINFO */ + + diff --git a/source/src/record.c b/source/src/record.c new file mode 100644 index 0000000..4088962 --- /dev/null +++ b/source/src/record.c @@ -0,0 +1,164 @@ + +/* + * record.c + * Type handler for the record/composite data type. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +int +pqt_put_record(PGtypeArgs *args) +{ + int i; + int len; + char *out; + PGparam *param = va_arg(args->ap, PGparam *); + + PUTNULLCHK(args, param); + + /* watch for invalidation issues */ + if (param->vcnt > args->typhandler->nattrs) + return args->errorf(args, + "param value count is %d but server says it's %d", + param->vcnt, args->typhandler->nattrs); + + /* Auto-fill the remaining fields with SQL NULLs. This feature was + * added because it was needed (by eSilo). We had a few cases where + * we needed to append new fields to existing composites but wanted + * to maintain backwards compatibility ... so some_func(mycomposite) + * would continue to work even for older versions unaware of the + * new composite fields. I guess for some this is unwanted behavior, + * but I think the cases for it are much more common. + */ + if (param->vcnt < args->typhandler->nattrs) + { + int nattrs = args->typhandler->nattrs - param->vcnt; + for (i=0; i < nattrs; i++) + pqt_putparam(param, NULL, 0, 0, BINARYFMT, + args->typhandler->attDescs[param->vcnt].attoid); + } + + /* column count, 4-byte integer */ + len = 4; + + /* determine total byte count to ensure args->put.out is large enough */ + for (i=0; i < param->vcnt; i++) + { + len += (4 + 4); /* oid + len */ + + if (param->vals[i].datal > 0) + len += param->vals[i].datal; + } + + /* ensure out buffer is large enough */ + if (args->put.expandBuffer(args, len) == -1) + return -1; + + out = args->put.out; + + /* write column count */ + pqt_buf_putint4(out, param->vcnt); + out += 4; + + for (i=0; i < param->vcnt; i++) + { + if (param->vals[i].format == 0) + return args->errorf(args, + "Cannot put composite attributes in text format"); + + if (param->vals[i].datal == NULL_LEN) + param->vals[i].oid = args->typhandler->attDescs[i].attoid; + + /* watch for invalidation issues */ + if (param->vals[i].oid != args->typhandler->attDescs[i].attoid) + return args->errorf(args, + "param value OID is %u but server says it's %u", + param->vals[i].oid, args->typhandler->attDescs[i].attoid); + + /* write column oid */ + pqt_buf_putint4(out, param->vals[i].oid); + out += 4; + + /* write column data length */ + pqt_buf_putint4(out, param->vals[i].datal); + out += 4; + + /* write the column data */ + if (param->vals[i].data && param->vals[i].datal > 0) + { + memcpy(out, param->vals[i].data, param->vals[i].datal); + out += param->vals[i].datal; + } + } + + return len; +} + +int +pqt_get_record(PGtypeArgs *args) +{ + int i; + int nattrs; + int vlen; + Oid server_oid; + DECLVALUE(args); + PGresult *res; + PGresult **resultp = va_arg(args->ap, PGresult **); + + CHKGETVALS(args, resultp); + + if (args->format == TEXTFMT) + return args->errorf(args, "record does not support text results"); + + /* get record column count, numAttributes */ + nattrs = pqt_buf_getint4(value); + value += 4; + + /* watch for invalidation issues */ + if (args->typhandler->nattrs != nattrs) + return args->errorf(args, + "type handler attribute count is %d but server says it's %d", + args->typhandler->nattrs, nattrs); + + if (!(res = pqt_copyresult(args, nattrs))) + RERR_MEM(args); + + for (i=0; i < nattrs; i++) + { + /* watch for invalidation issues */ + server_oid = (Oid) pqt_buf_getint4(value); + if (server_oid != args->typhandler->attDescs[i].attoid) + { + args->errorf(args, + "type handler attribute OID is %u but server says it's %u", + args->typhandler->attDescs[i].attoid, server_oid); + PQclear(res); + return -1; + } + + /* move past Oid */ + value += 4; + + /* get value length */ + vlen = pqt_buf_getint4(value); + value += 4; + + if (!PQsetvalue(res, 0, i, value, vlen)) + { + PQclear(res); + return -1; + } + + if (vlen > 0) + value += vlen; + } + + *resultp = res; + return 0; +} + diff --git a/source/src/regression-test.c b/source/src/regression-test.c new file mode 100644 index 0000000..e8845bb --- /dev/null +++ b/source/src/regression-test.c @@ -0,0 +1,1373 @@ + +#ifdef HAVE_CONFIG_H +# include "pqt_config.h" +#endif + +#define PLN do{ \ + printf("%s:%d\n", __FUNCTION__, __LINE__); \ + fflush(stdout); \ +}while(0) + +#if defined(_WIN32) || defined(_WIN64) +# define _CRT_SECURE_NO_WARNINGS /* shut up msvc8! */ +# ifdef _MSC_VER +# pragma warning (disable : 4127 4706) +# endif +# include +# include /* getnameinfo */ +# ifdef _MSC_VER +# define I64FMT "%I64d" +# else +# define I64FMT "%lld" +# endif +#else +# define SIGNAL_SUPPORT +# include +# include +# include +# include +# define I64FMT "%lld" +#endif + +#ifdef SIGNAL_SUPPORT +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef LLONG_MAX +# ifdef LONG_LONG_MAX +# define LLONG_MAX LONG_LONG_MAX +# else +# define LLONG_MAX 9223372036854775807LL +# endif +#endif + +#ifndef LLONG_MIN +# ifdef LONG_LONG_MIN +# define LLONG_MIN LONG_LONG_MIN +# else +# define LLONG_MIN -9223372036854775808LL +# endif +#endif + +#define RESERR PQresultErrorMessage(result) +#define CLEAR_TABLE(tname) PQclear(PQexec(conn, "TRUNCATE " tname)) +#define DROP_TABLE(tname) \ + PQclear(PQexec(conn, "DROP TABLE IF EXISTS " tname)) + +#define EXECOKAY(msg, status) do{ \ + if (PQresultStatus(result) != (status)) \ + { \ + fprintf(stderr, " **ERROR: %s - %s\n", msg, RESERR); \ + PQclear(result); \ + failcnt++; \ + return; \ + } \ +}while (0) + +#define CMDOKAY(msg) EXECOKAY(msg, PGRES_COMMAND_OK) +#define TUPSOKAY(msg) EXECOKAY(msg, PGRES_TUPLES_OK) + +#define PUTOKAY(paramobj, retval, msg) do{ \ + if ((retval) == 0){ \ + failcnt++; \ + fprintf(stderr, " **ERROR: %s - %s\n", msg, PQgeterror()); \ + return; \ + } \ +}while (0) + +#define GETOKAY(retval, msg) GETOKAY2(result, retval, msg) +#define GETOKAY2(_res, retval, msg) do{ \ + if ((retval) == 0){ \ + failcnt++; \ + fprintf(stderr, " **ERROR: %s - %s\n", msg, PQgeterror()); \ + PQclear(result); \ + return; \ + } \ +}while (0) + +#define CHKNUM(typspec, getval, putval) do{ \ + if ((getval) != (putval)) { \ + failcnt++; \ + fprintf(stderr, " %s - FAILED\n", typspec); \ + } \ + else \ + printf(" %s - passed\n", typspec); \ + testcnt++; \ +} while (0) + +#define CHKGEO(type) do{ \ + const char *typspec = "%" # type; \ + if (memcmp(& type, & type ## val, sizeof(type))) { \ + failcnt++; \ + fprintf(stderr, " %s - FAILED\n", typspec); \ + } \ + else \ + printf(" %s - passed\n", typspec); \ + testcnt++; \ +} while (0) + +#define CHKVLEN(typ, resval, inval) do{ \ + testcnt++; \ + if (strcmp(resval, inval)) { \ + failcnt++; \ + fprintf(stderr, " %%" typ " - FAILED\n"); \ + } \ + else \ + printf(" %%" typ " - passed\n"); \ +}while (0) + +static void cleanup(void); +static void test_natives(int format); +static void test_composite(void); +static void test_geometrics(int format); +static void test_varlen(int format); +static void test_datetime(int format); +static void test_network(int format); +static int epoch_put(PGtypeArgs *args); +static int epoch_get(PGtypeArgs *args); +static void test_subclass(int format); +static PGresult *execf(const char *cmdSpec, ...); +static int bpcharcmp(const char *put, const char *get, int charlen); + +#ifdef SIGNAL_SUPPORT +static void sighandler(int s); +#endif + +static void _notice_processor(void *arg, const char *message) +{/* do nothing */ + arg = NULL; + message = NULL; +} + + +static int testcnt = 0; +static int failcnt = 0; +static const char *datestyle = ""; +static PGconn *conn = NULL; +static PGparam *param = NULL; +static PGresult *result = NULL; + +#define ALIGN_NUM(val, mult) (((val) + ((mult) - 1)) & ~(((mult) - 1))) + +int main(int argc, char **argv) +{ + PGint4 n; + PGregisterType regtype; + char *conninfo = argc > 1 ? argv[1] : "hostaddr=127.0.0.1 user=postgres"; + + conn = PQconnectdb(conninfo); + if (PQstatus(conn) != CONNECTION_OK) + { + fprintf(stderr, "connection failure: %s\n", PQerrorMessage(conn)); + return 1; + } + + PQsetNoticeProcessor(conn, _notice_processor, NULL); + + if (!PQinitTypes(conn)) + { + fprintf(stderr, "Failed to initialize libpqtypes as " + "a libpq event proc\n"); + PQfinish(conn); + return 1; + } + + testcnt++; + regtype.typname = "bad_type"; + if(PQregisterTypes(conn, PQT_COMPOSITE, ®type, 1, 0)) + { + failcnt++; + fprintf(stderr, "Test bad type register failure case (FAILED)\n"); + } + + regtype.typname = "mytype_t"; + if(!PQregisterTypes(conn, PQT_COMPOSITE, ®type, 1, 0)) + fprintf(stderr, "%s\n", PQgeterror()); + + test_composite(); + + PQclear(PQexec(conn, "SET DateStyle TO 'ISO'")); + param = PQparamCreate(conn); + + /* test PQputvf - result should be 10 */ + testcnt++; + printf("\nPQputvf\n"); + result = execf("SELECT %int4 + (%int4 % %int4) AS answer", 9, 67, 3); + if (PQresultStatus(result) != PGRES_TUPLES_OK) + { + fprintf(stderr, " **ERROR: PQputvf - %s\n", PQgeterror()); + PQclear(result); + failcnt++; + return 1; + } + + PQgetf(result, 0, "#int4", "answer", &n); + PQclear(result); + if (n != 10) + { + failcnt++; + fprintf(stderr, " PQputvf - FAILED\n"); + } + else + { + printf(" PQputvf - passed\n"); + } + + testcnt++; + printf("\nPQspecPrepare\n"); + if(!PQspecPrepare(conn, "test", "SELECT %int8 + %int4", 1)) + { + failcnt++; + fprintf(stderr, " PQspecPrepare - FAILED (%s)\n", PQgeterror()); + } + else + { + char data[4]; + PGbytea ba; + PGint8 i8 = 45; + ba.len = 4; + ba.data = data; + + result = PQexecf(conn, "@test", i8, 4); + if(!result) + { + failcnt++; + fprintf(stderr, " PQspecPrepare - FAILED (%s)\n", PQgeterror()); + } + else + { + PGint8 i8out=0; + + PQgetf(result, 0, "%int8", 0, &i8out); + + if(i8 + 4 != i8out) + { + failcnt++; + fprintf(stderr, " PQspecPrepare - FAILED (expected " + "%lld but got %lld)\n", i8+4, i8out); + } + else + { + printf(" PQspecPrepare - passed\n"); + } + + PQclear(result); + } + } + + datestyle = PQparameterStatus(conn, "DateStyle"); + if (!datestyle) + datestyle = ""; + +#ifdef SIGNAL_SUPPORT + signal(SIGINT, sighandler); + signal(SIGILL, sighandler); + signal(SIGSEGV, sighandler); +# ifdef SIGFPE + signal(SIGFPE, sighandler); +# endif +# ifdef SIGBUS + signal(SIGBUS, sighandler); +# endif +#endif + + + test_natives(0); + test_natives(1); + test_varlen(0); + test_varlen(1); + test_geometrics(0); + test_geometrics(1); + test_network(0); + test_network(1); + + regtype.typname = "libpq.epoch=pg_catalog.timestamptz"; + regtype.typput = epoch_put; + regtype.typget = epoch_get; + + /* Test type sub-classing, use the epoch example from docs */ + if (!PQregisterTypes(conn, PQT_SUBCLASS, ®type, 1, 0)) + { + fprintf(stderr, "PQregisterTypes(epoch, subclass): %s\n", + PQerrorMessage(conn)); + } + else + { + test_subclass(0); + test_subclass(1); + } + + test_datetime(0); + test_datetime(1); + + /* datestyle is cached in PGparam. Since we need to change the datestyle, + * we destroy the current param and recreate it later. + */ + PQparamClear(param); + + /* Just tested ISO, test a non-ISO */ + PQclear(PQexec(conn, "SET DateStyle TO 'POSTGRES'")); + datestyle = PQparameterStatus(conn, "DateStyle"); + if (!datestyle) + datestyle = ""; + + param = PQparamCreate(conn); + test_datetime(0); + test_datetime(1); + cleanup(); + + printf("\nRan %d tests - %d failed\n", testcnt, failcnt); + return 1; +} + +/* Calls putf and executes cmdSpec. Returns a result object */ +static PGresult *execf(const char *cmdSpec, ...) +{ + int n; + PGresult *res; + va_list ap; + char stmt[4096]; + PGparam *prm = PQparamCreate(conn); + + va_start(ap, cmdSpec); + n = PQputvf(prm, stmt, sizeof(stmt), cmdSpec, ap); + va_end(ap); + + /* error message in PQparamErrorMessage */ + if (!n) + { + PQparamClear(prm); + return NULL; + } + + printf(" %s\n", stmt); + res = PQparamExec(conn, prm, stmt, 1); + PQparamClear(prm); + return res; +} + +/* Handle BPCHAR (Blank-Padded) SQL-type "character(n)" */ +static int bpcharcmp(const char *put, const char *get, int charlen) +{ + int i; + int len = (int)strlen(put); + + for (i=len; i < charlen; i++) + if (!isspace(get[i])) + return 1; + + return memcmp(put, get, len); +} + +static void cleanup(void) +{ + DROP_TABLE("libpq_natives"); + DROP_TABLE("libpq_varlen"); + DROP_TABLE("libpq_geos"); + DROP_TABLE("libpq_network"); + DROP_TABLE("libpq_datetime"); + DROP_TABLE("libpq_subclass"); + DROP_TABLE("libpq_composite"); + PQclear(PQexec(conn, "DROP TYPE complex")); + PQclear(PQexec(conn, "DROP TYPE simple")); + PQparamClear(param); + PQfinish(conn); +} + +/* make an effort to clean up */ +#ifdef SIGNAL_SUPPORT +static void sighandler(int s) +{ + cleanup(); + fprintf(stderr, "\n**Caught signal: %d ... aborting\n", s); + exit(1); +} +#endif + +static void test_composite(void) +{ + int i,x,r; + int ntups; + int inttups; + PGtext text; + PGparam *simple; + PGparam *complex; + PGparam *prm; + PGresult *simple_res; + PGarray intarr; + PGarray comparr; + PGchar buf[64]; + PGint4 a; + PGint8 b; + PGregisterType types[] = { + {"public.simple", NULL, NULL}, + {"public.complex", NULL, NULL} + }; + + printf("\nComposites & Arrays: (always binary)\n"); + + printf(" Testing empty array handling "); + PQclear(PQexec(conn, "DROP TABLE libpq_array")); + result = PQexec(conn, "CREATE TABLE libpq_array (arr int[])"); + CMDOKAY("creating libpq_array table:"); + PQclear(result); + + result = PQexec(conn, "INSERT INTO libpq_array VALUES ('{}')"); + CMDOKAY("inserting into libpq_array:"); + PQclear(result); + + result = PQparamExec(conn, NULL, "SELECT * FROM libpq_array", 1); + TUPSOKAY("executing select on libpq_array:"); + + r = PQgetf(result, 0, "%int4[]", 0, &intarr); + + GETOKAY(r, "Failed to get an empty array"); + PQclear(result); + + /* both values should be 0 */ + testcnt++; + if(PQntuples(intarr.res) != 0 || intarr.ndims != 0) + { + fprintf(stderr, "- FAILED\n ***ERROR: tuple and dimension " + "count should both be zero - ntups=%d, ndims=%d\n", + PQntuples(intarr.res), intarr.ndims); + failcnt++; + } + else + { + printf("- passed\n"); + } + PQclear(intarr.res); + + DROP_TABLE("libpq_composite"); + PQclear(PQexec(conn, "DROP TYPE public.complex")); + + PQclear(PQexec(conn, "DROP TYPE public.simple")); + result = PQexec(conn, "CREATE TYPE simple AS (a int4, b int8)"); + CMDOKAY("creating simple type"); + PQclear(result); + + result = PQexec(conn, "CREATE TYPE complex AS (t text, s simple, " + "arr int4[][])"); + CMDOKAY("creating complex type"); + PQclear(result); + + if (!PQregisterTypes(conn, PQT_COMPOSITE, types, 2, 0)) + { + fprintf(stderr, " **ERROR: PQregisterTypes(%s %s): %s\n", + types[0].typname, types[1].typname, PQgeterror()); + failcnt++; + return; + } + + result = PQexec(conn, "CREATE TABLE libpq_composite (comparr complex[])"); + CMDOKAY("creating libpq_composite table:"); + PQclear(result); + + simple = PQparamCreate(conn); + complex = PQparamCreate(conn); + prm = PQparamCreate(conn); + + /* array[10][3] */ + intarr.ndims = 2; + intarr.dims[0] = 10; + intarr.dims[1] = 3; + intarr.lbound[0] = 1; + intarr.lbound[1] = 1; + intarr.param = PQparamCreate(conn); + + /* If a 1D array is being used, you can avoid setting the dimension + * members of the PGarray structure by zeroing it. + */ + memset(&comparr, 0, sizeof(PGarray)); + comparr.param = PQparamCreate(conn); + + /* generate a complex[] containing 100 elements */ + for (i=0; i < 100; i++) + { + PGint8 val = ((PGint8)1<<48) + (PGint8)i; + + /* pack the fields of the 'simple' composite */ + r = PQputf(simple, "%int4 %int8", i+1000000, val); + PUTOKAY(simple, r, "putting simple attributes"); + + /* populate the complex.arr[10][3] */ + PQputf(intarr.param, "%null"); + for (x=1; x < 30; x++) + { + r= PQputf(intarr.param, "%int4", x+1); + PUTOKAY(intarr.param, r, "putting complex.arr element"); + } + + /* pack the fields of the 'complex' composite */ + sprintf((char *) buf, "text_%03d", i+1); + r = PQputf(complex, "%text %simple %int4[]", buf, simple, &intarr); + PUTOKAY(complex, r, "putting complex attributes"); + + /* now put element 'i' into the composite array */ + r = PQputf(comparr.param, "%complex", complex); + PUTOKAY(comparr.param, r, "putting complex[] element"); + + /* make sure to reset the params, otherwise you will continue to + * append parameters to below params. DO NOT reset the comparr.param, + * as this is the array we want to insert. + */ + PQparamReset(simple); + PQparamReset(intarr.param); + PQparamReset(complex); + + /* sneak in a NULL element. NULL composite array items + * used to be a bug. + */ + if(i == 49) + { + PQputf(comparr.param, "%complex", NULL); + i++; + } + } + + /* insert the complex[] */ + PQputf(prm, "%complex[]", &comparr); + result = PQparamExec(conn, prm, + "INSERT INTO libpq_composite VALUES ($1)", 1); + CMDOKAY("inserting into libpq_composite"); + PQclear(result); + + /* clear all params (order doesn't matter, they are + * isolated structures) + */ + PQparamClear(simple); + PQparamClear(intarr.param); + PQparamClear(complex); + PQparamClear(comparr.param); + PQparamClear(prm); + + /* select complex[] back out */ + result = PQparamExec(conn, NULL, "SELECT * FROM libpq_composite", 1); + TUPSOKAY("PQparamExec(SELECT:composite[])"); + + /* getf it into a PGarray */ + r = PQgetf(result, 0, "%complex[]", 0, &comparr); + GETOKAY(r, "PQgetf(complex[])"); + PQclear(result); + + /* composite arrays come back 1 tuple per element and 1 + * field per attribute + */ + ntups = PQntuples(comparr.res); + if (ntups != 100) + { + failcnt++; + fprintf(stderr, "put 100 elements into complex[] but " + "got back %d\n", ntups); + PQclear(comparr.res); + return; + } + + testcnt++; + for (i=0; i < ntups; i++) + { + /* Verify element 50 is NULL */ + if(i==50) + { + /* All fields should be NULL */ + if(!PQgetisnull(comparr.res, i, 0) || + !PQgetisnull(comparr.res, i, 1) || + !PQgetisnull(comparr.res, i, 2)) + { + failcnt++; + fprintf(stderr, " Element 50 should of been NULL\n"); + PQclear(comparr.res); + return; + } + + continue; + } + + /* get the complex composite at tuple 'i' */ + r = PQgetf(comparr.res, i, "%text %simple #int4[]", + 0, &text, 1, &simple_res, "arr", &intarr); + GETOKAY2(comparr.res, r, "PQgetf(comparr.res element)"); + + /* check the text field */ + sprintf((char *) buf, "text_%03d", i+1); + if (strcmp(text, (char *) buf)) + { + failcnt++; + fprintf(stderr, "complex[%d].t failed: sent=%s, recv=%s\n", + i, buf, text); + PQclear(simple_res); + PQclear(intarr.res); + PQclear(comparr.res); + return; + } + + /* Check the simple field: reference by fname, use '#' + * rather than '%' + */ + r = PQgetf(simple_res, 0, "#int4 #int8", "a", &a, "b", &b); + GETOKAY2(simple_res, r, "PQgetf(complex[].simple.*)"); + PQclear(simple_res); + + /* verify simple.* values */ + if (a != (i+1000000) || b != ((PGint8) 1<<48)+i) + { + failcnt++; + fprintf(stderr, "complex[%d].simple.* failed: " + "sent(a=%d, b=%lld), recv(a=%d, b=%lld)\n", + i, i+1000000, ((PGint8) 1<<48)+i, a, b); + PQclear(intarr.res); + PQclear(comparr.res); + return; + } + + /* check the int[10][3] field */ + if (intarr.dims[0] != 10 || intarr.dims[1] != 3 || + intarr.lbound[0] != 1 || intarr.lbound[1] != 1 || intarr.ndims != 2) + { + failcnt++; + fprintf(stderr, "complex[%d].int[][] dimension info failed: " + "sent(ndims=2, arr[1:10][1:3]), recv(ndims=%d, arr[%d:%d][%d:%d])\n", + i, intarr.ndims, intarr.lbound[0], intarr.dims[0], + intarr.lbound[1], intarr.dims[1]); + PQclear(intarr.res); + PQclear(comparr.res); + return; + } + + inttups = PQntuples(intarr.res); + for (x=1; x < inttups; x++) + { + r = PQgetf(intarr.res, x, "%int4", 0, &a); + GETOKAY2(intarr.res, r, "PQgetf(complex.int[][] element"); + + if (a != x+1) + { + failcnt++; + fprintf(stderr, " **ERROR: complex[%d].int4[][] sent=%d, recv=%d\n", + i, x+1, a); + PQclear(intarr.res); + PQclear(comparr.res); + return; + } + } + + PQclear(intarr.res); + } + + PQclear(comparr.res); + printf(" composite[] with nested composite and arrays passed\n"); +} + +static void test_natives(int format) +{ + int r; + PGchar ca; + PGchar cb; + PGint2 i2a, i2b; + PGint4 i4a, i4b; + PGint8 i8a, i8b; + PGfloat4 f4a, f4b; + PGfloat8 f8a, f8b; + PGmoney mona, monb; + Oid oida, oidb; + PGbool boola, boolb; + + PQparamReset(param); + printf("\nNative C types: (%s)\n", format ? "binary" : "text"); + + r = PQputf(param, "%char %char %int2 %int2 %int4 %int4 %int8 %int8 " + "%float4 %float4 %float8 %float8 %money %money %oid %oid %bool %bool", + -117, 224, -32000, 32000, INT_MIN, INT_MAX, LLONG_MIN, LLONG_MAX, + -1.23456f, 1.23456f, -123456789.654321, 123456789.654321, + (PGmoney)INT_MIN, 600000000054LL, 91982, 3000000000U, 0, 1); + PUTOKAY(param, r, "PQputf(natives)"); + + DROP_TABLE("libpq_natives"); + + result = PQexec(conn, "CREATE TABLE libpq_natives (" + "char_a \"char\", char_b \"char\", " + "i2_a int2, i2_b int2, " + "i4_a int4, i4_b int4, " + "i8_a int8, i8_b int8, " + "f4_a float4, f4_b float4, " + "f8_a float8, f8_b float8, " + "mon_a money, mon_b money, " + "oid_a oid, oid_b oid, " + "bool_a bool, bool_b bool)"); + CMDOKAY("creating libpq_natives table"); + PQclear(result); + + result = PQparamExec(conn, param, "INSERT INTO libpq_natives VALUES" + "($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18)", + format); + CMDOKAY("PQparamExec(INSERT:natives)"); + PQclear(result); + + result = PQparamExec(conn, NULL, "SELECT * FROM libpq_natives", format); + TUPSOKAY("PQparamExec(SELECT:natives)"); + + r = PQgetf(result, 0, "%char %char %int2 %int2 %int4 %int4 %int8 %int8 " + "%float4 %float4 %float8 %float8 %money, %money %oid %oid %bool %bool", + 0, &ca, 1, &cb, 2, &i2a, 3, &i2b, 4, &i4a, 5, &i4b, 6, &i8a, 7, &i8b, 8, + &f4a, 9, &f4b, 10, &f8a, 11, &f8b, 12, &mona, 13, &monb, 14, &oida, + 15, &oidb, 16, &boola, 17, &boolb); + GETOKAY(r, "PQgetf(natives)"); + PQclear(result); + + CHKNUM("%char", ca, -117); + CHKNUM("%char", (unsigned char) cb, 224); + CHKNUM("%int2", i2a, -32000); + CHKNUM("%int2", i2b, 32000); + CHKNUM("%int4", i4a, INT_MIN); + CHKNUM("%int4", i4b, INT_MAX); + CHKNUM("%int8", i8a, LLONG_MIN); + CHKNUM("%int8", i8b, LLONG_MAX); + CHKNUM("%float4", f4a, -1.23456f); + CHKNUM("%float4", f4b, 1.23456f); + CHKNUM("%float8", f8a, -123456789.654321); + CHKNUM("%float8", f8b, 123456789.654321); + CHKNUM("%money", mona, (PGmoney) INT_MIN); + CHKNUM("%money", monb, 600000000054LL); + CHKNUM("%oid", oida, 91982); + CHKNUM("%oid", oidb, 3000000000U); + CHKNUM("%bool", boola, 0); + CHKNUM("%bool", boolb, 1); +} + +static void test_geometrics(int format) +{ + int r; + static PGpoint ptarr[6] = {{565677.342, 999112.33344}, + {-565677.342, -999112.33344}, {-565677.342, 999112.33344}, + {565677.342, 999112.33344}, {-565677.342, -999112.33344}, + {-565677.342, 999112.33344}}; + PGpoint point = {0}; + PGpoint pointval = {565677.342, 999112.33344}; + PGlseg lseg = {{{0}}}; + PGlseg lsegval = {{{565677.342, -999112.33344}, {88876.2314, 77283.323}}}; + PGbox box = {{0}}; + PGbox boxval = {{565677.342, 88876.2314}, {77283.323, -999112.33344}}; + PGcircle circle = {{0,0},0}; + PGcircle circleval = {{7765.435, -7743.2993}, 23384.102}; + PGpath path = {0, 0, NULL}; + PGpath pathval = {6, 1, ptarr}; + PGpolygon polygon = {0, NULL}; + PGpolygon polygonval = {6, ptarr}; + + PQparamReset(param); + printf("\nGeometric types: (%s)\n", format ? "binary" : "text"); + + r = PQputf(param, "%point %lseg %box %circle %path %polygon", + &pointval, &lsegval, &boxval, &circleval, &pathval, &polygonval); + PUTOKAY(param, r, "PQputf(geos)"); + + DROP_TABLE("libpq_geos"); + + result = PQexec(conn, "CREATE TABLE libpq_geos (" + "p point, l lseg, b box, c circle, pa path, poly polygon)"); + CMDOKAY("creating libpq_geos table"); + PQclear(result); + + result = PQparamExec(conn, param, "INSERT INTO libpq_geos VALUES" + "($1,$2,$3,$4,$5,$6)", format); + CMDOKAY("PQparamExec(INSERT:geos)"); + PQclear(result); + + result = PQparamExec(conn, NULL, "SELECT * FROM libpq_geos", format); + TUPSOKAY("PQparamExec(SELECT:geos)"); + + r = PQgetf(result, 0, "%point %lseg %box %circle %path %polygon", + 0, &point, 1, &lseg, 2, &box, 3, &circle, 4, &path, 5, &polygon); + GETOKAY(r, "PQgetf(geos)"); + + CHKGEO(point); + CHKGEO(lseg); + CHKGEO(box); + CHKGEO(circle); + + testcnt++; + if (path.closed == pathval.closed && path.npts == pathval.npts && + memcmp(path.pts, pathval.pts, path.npts * sizeof(PGpoint))==0) + printf(" %%path - passed\n"); + else + { + failcnt++; + fprintf(stderr, " %%path - FAILED\n"); + } + + testcnt++; + if (polygon.npts == polygon.npts && + memcmp(polygon.pts, polygon.pts, polygon.npts * sizeof(PGpoint))==0) + printf(" %%polygon - passed\n"); + else + { + failcnt++; + fprintf(stderr, " %%polygon - FAILED\n"); + } + + /* NOTE: this will invalidate path.pts and polygon.pts */ + PQclear(result); +} + +/* + * numeric is tested here because it is always exposed as a string. + * uuid is not a varlen data type, but testing it here is ok + */ +static void test_varlen(int format) +{ + int r; + static char byteadata[] = {0, 1, 2, 3, 4, 5, 6, 7}; + PGbpchar bpcharp=NULL; + PGbpchar bpcharin = "character(n) blank-padded"; + PGvarchar varcharp=NULL; + PGvarchar varcharin = "character varying(n)"; + PGtext textp=NULL; + PGtext textin = "variable unlimited length"; + PGbytea byteap; + PGbytea byteain = {8, byteadata}; + PGuuid uuid; + char uuidin[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; + PGnumeric num; + PGnumeric numin = + "-62731893541288039212143296120112.12431212671229121291821928918211"; + + PQparamReset(param); + printf("\nVariable-length types: (%s)\n", format ? "binary" : "text"); + + r = PQputf(param, "%bpchar %bpchar* %varchar %varchar* " + "%text %text* %bytea %bytea* %uuid %numeric", + bpcharin, bpcharin, varcharin, varcharin, textin, textin, + &byteain, &byteain, uuidin, numin); + PUTOKAY(param, r, "PQputf(varlen)"); + + DROP_TABLE("libpq_varlen"); + + result = PQexec(conn, "CREATE TABLE libpq_varlen (" + "bp_a bpchar(32), bp_b bpchar(32), vc_a varchar(32), vc_b varchar(32), " + "text_a text, text_b text, bytea_a bytea, bytea_b bytea, " + "uid uuid, n numeric)"); + CMDOKAY("creating libpq_varlen table"); + PQclear(result); + + result = PQparamExec(conn, param, "INSERT INTO libpq_varlen VALUES" + "($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)", format); + CMDOKAY("PQparamExec(INSERT:varlen)"); + PQclear(result); + + result = PQparamExec(conn, NULL, + "SELECT bp_a,vc_a,text_a,bytea_a,uid,n FROM libpq_varlen", format); + TUPSOKAY("PQparamExec(SELECT:varlen)"); + + r = PQgetf(result, 0, + "%bpchar %varchar %text %bytea %uuid %numeric", + 0, &bpcharp, /* field_num, PGbpchar* */ + 1, &varcharp, /* field_num, PGvarchar* */ + 2, &textp, /* field_num, PGtext* */ + 3, &byteap, /* field_num, PGbytea* */ + 4, &uuid, /* field_num, PGuuid* */ + 5, &num); /* field_num, PGnumeric* */ + GETOKAY(r, "PQgetf(varlen)"); + + /* Because we are using the '*' specifier flag, we clear the results at + * the end. '*' flag is just a pointer to the result: like PQgetvalue(). + */ + + testcnt++; + if (bpcharcmp(bpcharin, bpcharp, 32)) + { + failcnt++; + fprintf(stderr, " %%bpchar - FAILED\n"); + } + else + printf(" %%bpchar - passed\n"); + + CHKVLEN("varchar", varcharp, varcharin); + CHKVLEN("text", textp, textin); + + testcnt++; + if (byteain.len != byteap.len || + memcmp(byteap.data, byteain.data, byteap.len)) + { + failcnt++; + fprintf(stderr, " %%bytea* - FAILED\n"); + } + else + printf(" %%bytea* - passed\n"); + + testcnt++; + if (memcmp(uuid, uuidin, 16)) + { + failcnt++; + fprintf(stderr, " %%uuid - FAILED\n"); + } + else + printf(" %%uuid - passed\n"); + + CHKVLEN("numeric", num, numin); + PQclear(result); +} + +static void test_datetime(int format) +{ + int r; + time_t t; + struct tm *tm; + char *tzn; + PGdate date[3] = {{0}}; + static PGdate dateval[] = { + {0, 1476, 9, 1, 2260432, 274, 0}, /* 1476-10-01 */ + {1, 1209, 7, 17, 1280076, 229, 1}, /* 1209-08-17 BC */ + {0, 2007, 11, 23, 2454458, 356, 0}}; /* 2007-12-23 */ + PGtime tim[3] = {{0}}; + static PGtime timeval[] = { + {23, 10, 31, 7214, 0, -1, 0, {0}}, /* 23:10:31.007214 */ + { 2, 19, 52, 0, 0, -1, 0, {0}}, /* 02:19:52 */ + {15, 57, 7, 881542, 0, -1, 0, {0}}}; /* 15:57:07.881542 */ + PGtime timetz[3] = {{0}}; + PGtime timetzval[] = { + {23, 10, 31, 9888, 1, -1, -28800, {0}}, /* 23:10:31.009888-08 */ + { 2, 19, 52, 0, 1, -1, 7200, {0}}, /* 02:19:52+02 */ + {15, 57, 7, 881542, 1, -1, -10800, {0}}}; /* 15:57:07.881542-03 */ + PGtimestamp timestamp[3] = {{0, {0}, {0}}}; + PGtimestamp timestampval[] = { + { -15565394969, {0, 1476, 9, 1, 2260432, 274, 0}, + {23, 10, 31, 7214, 0, -1, 0, {0}}}, /* 1476-10-01 23:10:31.007214 */ + {-100268228408, {1, 1209, 7, 17, 1280076, 229, 1}, + { 2, 19, 52, 0, 0, -1, 0, {0}}}, /* 1209-08-17 02:19:52 BC */ + { 1198425427, {0, 2007, 11, 23, 2454458, 356, 0}, + {15, 57, 7, 881542, 0, -1, 0, {0}}}}; /* 2007-12-23 15:57:07.881542 */ + PGinterval interval = {0}; + PGinterval intervalval = /* 2,354 years 8 mons 23 days 14:54:27.029333 */ + {2354, 8, 23, 14, 54, 27, 29333}; + PGtimestamp timestamptz; + PGtimestamp timestamptzval; + + PQparamReset(param); + memset(×tamptz, 0, sizeof(PGtimestamp)); + memset(×tamptzval, 0, sizeof(PGtimestamp)); + + /* Jan 1, 1980 (returned: text=server time zone, binary=localtime) */ + t = 315532800; + tm = localtime(&t); + timestamptzval.epoch = t; + timestamptzval.time.hour = tm->tm_hour; + timestamptzval.time.min = tm->tm_min; + timestamptzval.time.sec = tm->tm_sec; + timestamptzval.time.usec = 0; + timestamptzval.time.withtz = 1; + timestamptzval.date.isbc = 0; + timestamptzval.date.year = tm->tm_year + 1900; + timestamptzval.date.mon = tm->tm_mon; + timestamptzval.date.mday = tm->tm_mday; + PQlocalTZInfo(&t, ×tamptzval.time.gmtoff, + ×tamptzval.time.isdst, &tzn); + strcpy(timestamptzval.time.tzabbr, tzn); + + strcpy(timetzval[0].tzabbr, "GMT-0800"); + strcpy(timetzval[1].tzabbr, "GMT+0200"); + strcpy(timetzval[2].tzabbr, "GMT-0300"); + + printf("\nDate & Time types: (%s '%s')\n", + format ? "binary" : "text", datestyle); + + r = PQputf(param, "%date %date %date %time %time %time %timetz %timetz " + "%timetz %timestamp %timestamp %timestamp %interval %timestamptz", + &dateval[0], &dateval[1], &dateval[2], &timeval[0], &timeval[1], + &timeval[2], &timetzval[0], &timetzval[1], &timetzval[2], + ×tampval[0], ×tampval[1], ×tampval[2], &intervalval, + ×tamptzval); + PUTOKAY(param, r, "PQputf(datetime)"); + + DROP_TABLE("libpq_datetime"); + + result = PQexec(conn, "CREATE TABLE libpq_datetime (" + "date_a date, date_b date, date_c date, " + "time_a time, time_b time, time_c time, " + "timetz_a timetz, timetz_b timetz, timetz_c timetz, " + "timestamp_a timestamp, timestamp_b timestamp, timestamp_c timestamp, " + "intvl interval, tstz timestamptz)"); + CMDOKAY("creating libpq_datetime table"); + PQclear(result); + + result = PQparamExec(conn, param, "INSERT INTO libpq_datetime VALUES" + "($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)", format); + CMDOKAY("PQparamExec(INSERT:datetime)"); + PQclear(result); + + result = PQparamExec(conn, NULL, "SELECT * FROM libpq_datetime", format); + TUPSOKAY("PQparamExec(SELECT:datetime)"); + + r = PQgetf(result, 0, "%date %date %date %time %time %time %timetz " + "%timetz %timetz %timestamp %timestamp %timestamp %interval " + "%timestamptz", + 0, &date[0], 1, &date[1], 2, &date[2], + 3, &tim[0], 4, &tim[1], 5, &tim[2], + 6, &timetz[0], 7, &timetz[1], 8, &timetz[2], + 9, ×tamp[0], 10, ×tamp[1], 11, ×tamp[2], + 12, &interval, 13, ×tamptz); + GETOKAY(r, "PQgetf(datetime)"); + PQclear(result); + + for (r=0; r < 3; r++) + { + testcnt++; + if (memcmp(&date[r], &dateval[r], sizeof(PGdate)) == 0) + printf(" %%date - #%d passed\n", r+1); + else + { + failcnt++; + fprintf(stderr, " %%date - #%d FAILED\n", r+1); + } + } + + for (r=0; r < 3; r++) + { + testcnt++; + if (memcmp(&tim[r], &timeval[r], sizeof(PGtime)) == 0) + printf(" %%time - #%d passed\n", r+1); + else + { + failcnt++; + fprintf(stderr, " %%time - #%d FAILED\n", r+1); + } + } + + for (r=0; r < 3; r++) + { + testcnt++; + if (memcmp(&timetz[r], &timetzval[r], sizeof(PGtime)) == 0) + printf(" %%timetz - #%d passed\n", r+1); + else + { + failcnt++; + fprintf(stderr, " %%timetz - #%d FAILED\n", r+1); + } + } + + for (r=0; r < 3; r++) + { + + testcnt++; + if (memcmp(×tamp[r], ×tampval[r], sizeof(PGtimestamp)) == 0) + { + printf(" %%timestamp - #%d passed\n", r+1); + } + /* Windows may hit rounding issues with usecs. In this test, usecs of + * 7214 were coming back as 7215. + */ +#if defined(_WIN32) || defined(_WIN64) + else if (timestamp[r].time.usec+1 == timestampval[r].time.usec || + timestamp[r].time.usec-1 == timestampval[r].time.usec) + { + printf(" %%timestamp - #%d passed (warning: known usec rounding " + "issue - sent=%d, recv=%d)\n", r+1, + timestampval[r].time.usec, timestamp[r].time.usec); + } +#endif + else + { + failcnt++; + fprintf(stderr, " %%timestamp - #%d FAILED\n", r+1); + } + } + + /* interval usecs won't match when using a non-ISO datastyle because only + * 2 fractional digits are provided: 23.291876 => 23.29 secs. + */ + if (format == 0 && !strstr(datestyle, "ISO")) + { + printf(" **NOTE: non-ISO DateStyle interval text has a 2 digit limit " + "on the microsecond value ... adjusting\n => sent=%d, " + "received=%d\n", intervalval.usecs, interval.usecs); + interval.usecs = intervalval.usecs; + } + + testcnt++; + if (memcmp(&interval, &intervalval, sizeof(PGinterval)) == 0) + printf(" %%interval - passed\n"); + else + { + failcnt++; + fprintf(stderr, " %%interval - FAILED\n"); + + printf(" SENT: yr=%d,mon=%d,days=%d,hr=%d,min=%d,sec=%d,usec=%d\n", + intervalval.years, intervalval.mons, intervalval.days, + intervalval.hours, intervalval.mins, intervalval.secs, + intervalval.usecs); + printf(" RECV: yr=%d,mon=%d,days=%d,hr=%d,min=%d,sec=%d,usec=%d\n", + interval.years, interval.mons, interval.days, + interval.hours, interval.mins, interval.secs, interval.usecs); + } + + testcnt++; + + if (timestamptz.epoch == timestamptzval.epoch) + printf(" %%timestamptz - passed\n"); + else + { + failcnt++; + fprintf(stderr, " %%timestamptz - FAILED\n"); + + printf("SENT: epoch=%lld,isbc=%d,yr=%d,mon=%d,mday=%d,jday=%d," + "yday=%d,wday=%d,hr=%d,min=%d,sec=%d,usec=%d,withtz=%d," + "isdst=%d,tzabbr=%s,gmtoff=%d\n", + timestamptzval.epoch, + timestamptzval.date.isbc,timestamptzval.date.year, + timestamptzval.date.mon, + timestamptzval.date.mday,timestamptzval.date.jday, + timestamptzval.date.yday, + timestamptzval.date.wday, + timestamptzval.time.hour, timestamptzval.time.min, + timestamptzval.time.sec, + timestamptzval.time.usec, timestamptzval.time.withtz, + timestamptzval.time.isdst, + timestamptzval.time.tzabbr, timestamptzval.time.gmtoff); + + printf("RECV: epoch=%lld,isbc=%d,yr=%d,mon=%d,mday=%d,jday=%d," + "yday=%d,wday=%d,hr=%d,min=%d,sec=%d,usec=%d,withtz=%d,isdst=%d," + "tzabbr=%s,gmtoff=%d\n", + timestamptz.epoch, + timestamptz.date.isbc,timestamptz.date.year,timestamptz.date.mon, + timestamptz.date.mday,timestamptz.date.jday,timestamptz.date.yday, + timestamptz.date.wday, + timestamptz.time.hour, timestamptz.time.min, timestamptz.time.sec, + timestamptz.time.usec, timestamptz.time.withtz, timestamptz.time.isdst, + timestamptz.time.tzabbr, timestamptz.time.gmtoff); + } +} + +static void test_network(int format) +{ + int r; + PGinet ipv4, ipv4out; + PGinet ipv6, ipv6out; + PGmacaddr mac = {1,2,3,225,226,227}; + PGmacaddr macout; + int have_ipv4=1; + int have_ipv6=1; + +#if defined(_MSC_VER) || defined(__MINGW32__) || defined(HAVE_GETADDRINFO) + struct addrinfo *info = NULL; + struct addrinfo hints = {0}; + + memset(&ipv4, 0, sizeof(PGinet)); + memset(&ipv4out, 0, sizeof(PGinet)); + memset(&ipv6, 0, sizeof(PGinet)); + memset(&ipv6out, 0, sizeof(PGinet)); + + PQparamReset(param); + printf("\nNetwork types: (%s)\n", format ? "binary" : "text"); + + /* this should always work */ + if ((r = getaddrinfo("192.168.123.77", NULL, NULL, &info))) + { + have_ipv4 = 0; + //testcnt++; + //failcnt++; + fprintf(stderr, " IPv4: SKIPPING TEST (no support)\n"); + } + else + { + ipv4.mask = 32; + ipv4.sa_buf_len = (int)info->ai_addrlen; + ipv4.is_cidr = 0; + memcpy(ipv4.sa_buf, info->ai_addr, ipv4.sa_buf_len); + memset(ipv4.sa_buf, 0, 2); + ((struct sockaddr *) ipv4.sa_buf)->sa_family = AF_INET; + freeaddrinfo(info); + } + +#ifdef AF_INET6 + hints.ai_family = AF_INET6; +#endif + + /* windows may choke on an IPv6 address */ + if ((r = getaddrinfo("2001:4f8:3:ba:2e0:81ff:fe22:d1f1", + NULL, &hints, &info))) + { + have_ipv6 = 0; + //testcnt++; + //failcnt++; + fprintf(stderr, " IPv6: SKIPPING TEST (no support - [%d] %s)\n", + r, gai_strerror(r)); + } + else + { + ipv6.mask = 32; + ipv6.sa_buf_len = (int)info->ai_addrlen; + ipv6.is_cidr = 0; + memcpy(ipv6.sa_buf, info->ai_addr, ipv6.sa_buf_len); + +#ifdef AF_INET6 + memset(ipv6.sa_buf, 0, 2); + ((struct sockaddr *) ipv6.sa_buf)->sa_family = AF_INET6; +#endif + + freeaddrinfo(info); + } + + r = have_ipv4 ? PQputf(param, "%inet", &ipv4) : PQputf(param, "%null"); + PUTOKAY(param, r, "PQputf(network-ipv4)"); + + r = have_ipv6 ? PQputf(param, "%inet", &ipv6) : PQputf(param, "%null"); + PUTOKAY(param, r, "PQputf(network-ipv6)"); + + r = PQputf(param, "%macaddr", &mac); + PUTOKAY(param, r, "PQputf(network-macaddr)"); + + DROP_TABLE("libpq_network"); + + result = PQexec(conn, "CREATE TABLE libpq_network (" + "ipv4 inet, ipv6 inet, mac macaddr)"); + CMDOKAY("creating libpq_network table"); + PQclear(result); + + result = PQparamExec(conn, param, + "INSERT INTO libpq_network VALUES ($1,$2,$3)", format); + CMDOKAY("PQparamExec(INSERT:network)"); + PQclear(result); + + result = PQparamExec(conn, NULL, "SELECT * FROM libpq_network", format); + TUPSOKAY("PQparamExec(SELECT:network)"); + + r = PQgetf(result, 0, "%inet %inet %macaddr", + 0, &ipv4out, 1, &ipv6out, 2, &macout); + GETOKAY(r, "PQgetf(network)"); + + PQclear(result); + + if (have_ipv4) + { + if (memcmp(&ipv4out, &ipv4, sizeof(PGinet))) + { + failcnt++; + fprintf(stderr, " %%inet - IPv4 FAILED\n"); + } + else + printf(" %%inet - IPv4 passed\n"); + } + + if (have_ipv6) + { + if (memcmp(&ipv6out, &ipv6, sizeof(PGinet))) + { + failcnt++; + fprintf(stderr, " %%inet - IPv6 FAILED\n"); + } + else + printf(" %%inet - IPv6 passed\n"); + } + + testcnt++; + if (memcmp(&macout, &mac, sizeof(PGmacaddr))) + { + failcnt++; + fprintf(stderr, " %%macaddr - FAILED\n"); + } + else + printf(" %%macaddr - passed\n"); +#else + printf("\nNetwork types: (%s) - skipping, no getaddrinfo()\n", + format ? "binary" : "text"); +#endif +} + +/* PGtypeProc put for epoch sub-class */ +static int epoch_put(PGtypeArgs *args) +{ + PGtimestamp ts; + time_t t = va_arg(args->ap, time_t); + struct tm *tm = localtime(&t); + + printf(" epoch_put('%ld')\n", t); + + ts.date.isbc = 0; + ts.date.year = tm->tm_year + 1900; /* PGtimestamp requires 4-digit year */ + 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; + + PQlocalTZInfo(&t, &ts.time.gmtoff, &ts.time.isdst, NULL); + return args->super(args, &ts); +} + +/* PGtypeProc get for epoch sub-class */ +static int epoch_get(PGtypeArgs *args) +{ + PGtimestamp ts; + time_t *t = va_arg(args->ap, time_t *); + + printf(" epoch_get(%s)\n", args->format==0 ? "text" : "binary"); + + if (args->super(args, &ts) == -1) + return -1; /* args->errorf called by super class */ + + /* since PGtimestamp contains an epoch member as an int8, we can + * just copy that value, rather than doing a mktime(). If time_t + * is 32-bits, the below has a limited range compared to an int8. + * Although, this will work fine for timestamps in a 32-bit time_t range. + * 64-bit time_t should always work. + */ + *t = (time_t)ts.epoch; + return 0; +} + +static void test_subclass(int format) +{ + int r; + PGparam *prm; + time_t timeout[2] = {0}; + /* 1970-01-01 00:00:00, 2000-01-01 00:00:00 */ + time_t timevals[] = {0, 946702800}; + + printf("\nSub-class: (%s)\n", format ? "binary" : "text"); + + DROP_TABLE("libpq_subclass"); + result = PQexec(conn, + "CREATE TABLE libpq_subclass (n timestamptz, a timestamptz)"); + CMDOKAY("creating libpq_subclass table"); + PQclear(result); + + prm = PQparamCreate(conn); + if (!PQputf(prm, "%epoch %epoch", timevals[0], timevals[1])) + fprintf(stderr, "subclass_put failed: %s\n", PQgeterror()); + result = PQparamExec(conn, prm, "INSERT INTO libpq_subclass " + "VALUES ($1, $2)", format); + PQparamClear(prm); + CMDOKAY("PQparamExec(INSERT:subclass)"); + PQclear(result); + + result = PQparamExec(conn, NULL, "SELECT * FROM libpq_subclass", format); + TUPSOKAY("PQparamExec(SELECT:subclass)"); + + /* use schema-qualified name */ + r = PQgetf(result, 0, "%libpq.epoch %epoch", + 0, &timeout[0], 1, &timeout[1]); + GETOKAY(r, "PQgetf(subclass)"); + PQclear(result); + + testcnt++; + if (memcmp(timeout, timevals, sizeof(timeout))) + { + failcnt++; + fprintf(stderr, " %%epoch - FAILED (putval=%ld,%ld, getval=%ld,%ld)\n", + timevals[0], timevals[1], timeout[0], timeout[1]); + } + else + fprintf(stderr, " %%epoch - passed\n"); +} + + diff --git a/source/src/spec.c b/source/src/spec.c new file mode 100644 index 0000000..6c5a97e --- /dev/null +++ b/source/src/spec.c @@ -0,0 +1,660 @@ + +/* + * spec.c + * Type Specifier parser and compiler. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +/* For use with pqt_parse */ +#define CHKSTMTBUF(nbytes_add) do{ \ + if ((*stmtPos + (nbytes_add)) >= stmtBufLen) \ + { \ + PQseterror("statement buffer is too small"); \ + return FALSE; \ + } \ +}while (0) + +/* For use with PQspecPrepare */ +#define FREESTMTBUF do{ \ + if (stmtBuf && stmtBuf != buffer) \ + free(stmtBuf); \ +}while(0) + +static char * +skipQuotes(char *s); + +static char * +parseId(char *id, char **start, int *len, int *flags, int typpos); + +static int +expandSpecs(PGtypeData *typeData); + +int +PQspecPrepare(PGconn *conn, const char *name, + const char *format, int is_stmt) +{ + int flags; + int typpos = 0; + int idmax = 0; + size_t stmtPos = 0; + PGtypeHandler *h; + PGtypeData *typeData; + PGtypeSpec *spec; + size_t stmtBufLen = 0; + char *stmtBuf = NULL; + char buffer[8192]; + + if (!conn) + { + PQseterror("PGConn cannot be NULL"); + return FALSE; + } + + if (!name || !*name) + { + PQseterror("Prepared specifier name cannot be NULL or an empty string"); + return FALSE; + } + + if (format && !*format) + { + PQseterror("Specifier format string cannot be empty"); + return FALSE; + } + + if (!isalnum(*name) && *name != '_') + { + PQseterror("Prepared specifier name must begin with an alpha, " + "number or underscore."); + return FALSE; + } + + typeData = PQinstanceData(conn, pqt_eventproc); + if (!typeData) + { + PQseterror("No type data exists for PGconn at %p", conn); + return FALSE; + } + + /* This is a removal request */ + if (!format) + { + int i; + + for (i=0; i < typeData->typspeccnt; i++) + { + if (strcmp(typeData->typspecs[i].name, name) == 0) + { + /* clear it */ + pqt_clearspec(&typeData->typspecs[i]); + + /* remove from list, not needed if its the last element */ + if (i != typeData->typspeccnt - 1) + memmove(typeData->typspecs + i, typeData->typspecs + i + 1, + (typeData->typspeccnt - i - 1) * sizeof(PGtypeSpec)); + + typeData->typspeccnt--; + break; + } + } + + /* always return TRUE, an error is not useful here */ + return TRUE; + } + + /* Already exists case */ + spec = pqt_getspec(typeData->typspecs, typeData->typspeccnt, name); + if (spec) + { + PQseterror("Prepared spec already exists '%s'", name); + return FALSE; + } + + /* Make sure specs array is large enough */ + if (!expandSpecs(typeData)) + return FALSE; + + spec = &typeData->typspecs[typeData->typspeccnt]; + + /* cache statement along with prepared type spec */ + if (is_stmt) + { + stmtBufLen = strlen(format) + 1; + + /* no room in stack, use heap */ + if (stmtBufLen > sizeof(buffer)) + { + stmtBuf = (char *) malloc(stmtBufLen); + if (!stmtBuf) + { + PQseterror(PQT_OUTOFMEMORY); + return FALSE; + } + } + else + { + stmtBuf = buffer; + stmtBufLen = sizeof(buffer); + } + } + + while (format && *format) + { + format = pqt_parse(format, typeData->typhandlers, typeData->typhcnt, + stmtBuf, stmtBufLen, &h, &stmtPos, &typpos, &flags); + + if (!format) + { + pqt_clearspec(spec); + FREESTMTBUF; + return FALSE; + } + + /* skipped harmless chars in format, like quoted sections. */ + if(!h) + continue; + + if (!spec->idlist || spec->idcnt == idmax) + { + int c = idmax ? idmax * 2 : 8; + void *p = pqt_realloc(spec->idlist, c * sizeof(int)); + + if (!p) + { + PQseterror(PQT_OUTOFMEMORY); + pqt_clearspec(spec); + FREESTMTBUF; + return FALSE; + } + + spec->idlist = (int *) p; + + p = pqt_realloc(spec->flags, c * sizeof(char)); + if (!p) + { + PQseterror(PQT_OUTOFMEMORY); + pqt_clearspec(spec); + FREESTMTBUF; + return FALSE; + } + + spec->flags = (unsigned char *) p; + idmax = c; + } + + /* Parallel arrays, every handler needs type flags */ + spec->idlist[spec->idcnt] = h->id; + spec->flags[spec->idcnt++] = (unsigned char) flags; + } + + /* terminate stmtBuf, guarenteed to have room for NUL */ + if (stmtBuf) + stmtBuf[stmtPos] = 0; + + /* copy name string */ + spec->name = strdup(name); + if (!spec->name) + { + pqt_clearspec(spec); + PQseterror(PQT_OUTOFMEMORY); + FREESTMTBUF; + return FALSE; + } + + /* copy the parameterized stmt string */ + if (stmtBuf) + { + spec->stmt = strdup(stmtBuf); + if (!spec->stmt) + { + pqt_clearspec(spec); + PQseterror(PQT_OUTOFMEMORY); + FREESTMTBUF; + return FALSE; + } + } + + FREESTMTBUF; + + /* Success, increment type spec count */ + typeData->typspeccnt++; + return TRUE; +} + +int +PQclearSpecs(PGconn *conn) +{ + PGtypeData *typeData; + + if (!conn) + { + PQseterror("PGConn cannot be NULL"); + return FALSE; + } + + typeData = PQinstanceData(conn, pqt_eventproc); + if (!typeData) + { + PQseterror("No type data exists for PGconn at %p", conn); + return FALSE; + } + + pqt_freespecs(typeData->typspecs, typeData->typspeccnt); + typeData->typspecs = NULL; + typeData->typspeccnt = 0; + typeData->typspecmax = 0; + + return TRUE; +} + +char *pqt_parse(const char *format, PGtypeHandler *h, int hcnt, + char *stmtBuf, size_t stmtBufLen, PGtypeHandler **out, size_t *stmtPos, + int *typpos, int *flags) +{ + int specMark; + char *s = skipQuotes((char *) format); + char typname[PQT_MAXIDLEN + 1]; + char schema[PQT_MAXIDLEN + 1]; + char tmp[200]; + + *out = NULL; + + if (!s) + return NULL; + + /* found quotes to skip */ + if (s != format) + { + if (stmtBuf) + { + size_t n = s - format; + CHKSTMTBUF(n); + memcpy(stmtBuf + *stmtPos, format, n); + (*stmtPos) += n; + } + + return s; + } + + specMark = *format; + if (specMark != '%' && specMark != '#') + { + if (stmtBuf) + { + CHKSTMTBUF(1); + stmtBuf[*stmtPos] = *format; + (*stmtPos)++; + } + + format++; + return (char *) format; + } + + /* spec skips % or # */ + if (!(s = pqt_parsetype(format + 1, schema, typname, flags, *typpos + 1))) + return NULL; + + if (*flags & TYPFLAG_INVALID) + { + if (stmtBuf) + { + CHKSTMTBUF(1); + stmtBuf[*stmtPos] = *format++; + (*stmtPos)++; + PQseterror(NULL); /* set by pqt_parsetype */ + return (char *) format; + } + + return NULL; + } + + (*typpos)++; + + if (!(*out = pqt_gethandler(h, hcnt, schema, typname))) + { + PQseterror("Uknown type '%s' (position %d)", + pqt_fqtn(tmp, sizeof(tmp), schema, typname), *typpos); + return NULL; + } + + if (stmtBuf) + { + int n = pqt_snprintf(tmp, sizeof(tmp), "$%d", *typpos); + CHKSTMTBUF(n); + memcpy(stmtBuf + *stmtPos, tmp, n); + (*stmtPos) += n; + } + + if (!(*out)->typput) + { + PGtypeHandler *o = pqt_gethandlerbyid(h, hcnt, (*out)->base_id); + if (!o || !o->typput) + { + PQseterror( + "Type '%s' doesn't support put operations (position %d)", + pqt_fqtn(tmp, sizeof(tmp), (*out)->typschema, + (*out)->typname), *typpos); + + *out = NULL; + return NULL; + } + + *out = o; + } + +#if 0 + if ((*flags & TYPFLAG_POINTER) && !pqt_allowsptr(*out)) + { + PQseterror( + "Type '%s' doesn't support putting pointers (position %d)", + pqt_fqtn(tmp, sizeof(tmp), (*out)->typschema, + (*out)->typname), *typpos); + + *out = NULL; + return NULL; + } +#endif + + if (specMark == '#') + (*flags) |= TYPFLAG_BYNAME; + + return s; +} + +void +pqt_clearspec(PGtypeSpec *spec) +{ + if (spec->name) + free(spec->name); + + if (spec->stmt) + free(spec->stmt); + + if (spec->idlist) + free(spec->idlist); + + if (spec->flags) + free(spec->flags); + + memset(spec, 0, sizeof(PGtypeSpec)); +} + +PGtypeSpec *pqt_dupspecs(PGtypeSpec *specs, int count) +{ + int i; + PGtypeSpec *new_specs = (PGtypeSpec *) malloc(count * sizeof(PGtypeSpec)); + + if (!new_specs) + return NULL; + + memset(new_specs, 0, count * sizeof(PGtypeSpec)); + + for (i=0; i < count; i++) + { + PGtypeSpec *s = &specs[i]; + PGtypeSpec *news = &new_specs[i]; + + news->idcnt = s->idcnt; + + news->name = strdup(s->name); + if (!news->name) + { + pqt_freespecs(new_specs, i+1); + return NULL; + } + + if(s->stmt) + { + news->stmt = strdup(s->stmt); + if (!news->stmt) + { + pqt_freespecs(new_specs, i+1); + return NULL; + } + } + + news->idlist = (int *) malloc(s->idcnt * sizeof(int)); + if (!news->idlist) + { + pqt_freespecs(new_specs, i+1); + return NULL; + } + + memcpy(news->idlist, s->idlist, s->idcnt * sizeof(int)); + + news->flags = (unsigned char *) malloc(s->idcnt * sizeof(char)); + if (!news->flags) + { + pqt_freespecs(new_specs, i+1); + return NULL; + } + + memcpy(news->flags, s->flags, s->idcnt * sizeof(char)); + } + + return new_specs; +} + +void pqt_freespecs(PGtypeSpec *specs, int count) +{ + int i; + + for (i=0; i < count; i++) + pqt_clearspec(&specs[i]); + + if (specs) + free(specs); +} + +PGtypeSpec *pqt_getspec(PGtypeSpec *specs, int count, const char *name) +{ + int i; + + for (i=0; i < count; i++) + if (strcmp(specs[i].name, name) == 0) + return &specs[i]; + + return NULL; +} + +/* Parse a type identifer name (schema qualified or not) from spec. spec + * must point to the first char after the % sign, which maybe a + * double quote. + * + * spec - pointer to typname, just after the '%' or '#' + * schema - buffer to receive schema (PQT_MAXIDLEN bytes) + * typname - buffer to receive typname (PQT_MAXIDLEN bytes) + * flags - a pointer to an int that is set one or more TYPFLAG_xxx + * typpos - 1-based position of spec in specifier string (0 for unknown) + */ +char * +pqt_parsetype(const char *spec, char *schema, char *typname, + int *flags, int typpos) +{ + int i; + char *start; + int len=0; + char *s = (char *)spec; + + if (!(s = parseId(s, &start, &len, flags, typpos))) + return NULL; + + /* not a valid specifer, false positive like "(x % y) = 0" */ + if (*flags & TYPFLAG_INVALID) + return s; + + *schema = 0; + if (*s == '.') + { + memcpy(schema, start, len); + schema[len] = 0; + if (*flags & TYPFLAG_CASEFOLD) + for (i=0; i < len; i++) + schema[i] = pqt_tolower(schema[i]); + + /* now get typname */ + if (!(s = parseId(++s, &start, &len, flags, typpos))) + return NULL; + + if (*flags & TYPFLAG_INVALID) + return s; + } + + memcpy(typname, start, len); + typname[len] = 0; + if (*flags & TYPFLAG_CASEFOLD) + for (i=0; i < len; i++) + typname[i] = pqt_tolower(typname[i]); + + return s; +} + +static char * +parseId(char *id, char **start, int *len, int *flags, int typpos) +{ + char *p = id; + + *flags = 0; + *start = NULL; + *len = 0; + + if (*p == '"') + p++; + + /* check first character */ + if (!isalpha(*p) && *p != '_') + { + *flags |= TYPFLAG_INVALID; + PQseterror( + "Invalid first character for identifier '%c' (pos:%d)", *p, typpos); + return p; + } + + if (*id == '"') + { + id++; + if (!(p = strchr(id, '"'))) + { + *flags |= TYPFLAG_INVALID; + PQseterror("Unterminated double quote '%s' (pos:%d)", + id-1, typpos); + return p; + } + + *len = (int) (p - id); + *start = id; + p++; + } + else + { + for (p=id+1; isalnum(*p) || *p=='_'; p++) ; + + *len = (int) (p - id); + *start = id; + *flags |= TYPFLAG_CASEFOLD; + } + + /* range check */ + if (*len == 0 || *len > PQT_MAXIDLEN) + { + *flags |= TYPFLAG_INVALID; + PQseterror("Identifier out of range %d (pos:%d), range is 1 to %d", + *len, typpos, PQT_MAXIDLEN); + return p; + } + + /* direct pointer request */ + if (*p == '*') + { + p++; + *flags |= TYPFLAG_POINTER; + } + + /* Is this an array? Ex. %int4[] or %"a b"[] */ + if (p[0] == '[' && p[1] == ']') + { + if (*flags & TYPFLAG_POINTER) + { + PQseterror( + "'*' specifer flag cannot be used with arrays[] '%s' (pos:%d)", + id, typpos); + return NULL; + } + + *flags |= TYPFLAG_ARRAY; + p += 2; + } + + return p; +} + +static int +expandSpecs(PGtypeData *typeData) +{ + int n; + PGtypeSpec *specs; + + if (typeData->typspeccnt < typeData->typspecmax) + return TRUE; + + n = typeData->typspecmax ? (typeData->typspecmax * 3) / 2 : 8; + + specs = (PGtypeSpec *) pqt_realloc( + typeData->typspecs, sizeof(PGtypeSpec) * n); + + if (!specs) + { + PQseterror(PQT_OUTOFMEMORY); + return FALSE; + } + + memset(specs + typeData->typspeccnt, 0, + (n - typeData->typspeccnt) * sizeof(PGtypeSpec)); + + typeData->typspecs = specs; + typeData->typspecmax = n; + return TRUE; +} + +/* skip quoted strings. Doesn't need to account for E'' syntax. The + * E is copied over prior to the quoted string. + * + * Returns a pointer to the next character after the closing quote or + * NULL if there was an error. + */ +static char * +skipQuotes(char *s) +{ + char *end; + + if (*s != '\'') + return s; + + end = s; + while (*++end) + { + /* If we see a backslash, skip an extra char. No need to dig any + * further since this method works with \digits and \hex. + */ + if (*end == '\\') + end++; + else if (*end == '\'') + break; + } + + /* unterminated quote */ + if (!*end) + { + PQseterror("unterminated single quoted string"); + return NULL; + } + + return ++end; /* skip ending quote */ +} + diff --git a/source/src/utils.c b/source/src/utils.c new file mode 100644 index 0000000..2ff0e7b --- /dev/null +++ b/source/src/utils.c @@ -0,0 +1,265 @@ + +/* + * util.c + * Utility functions. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +PGresult * +pqt_copyresult(PGtypeArgs *args, int nattrs) +{ + int i; + PGresult *res; + int tableid, columnid, format; + PGresAttDesc *ad = (PGresAttDesc *) malloc(nattrs * sizeof(PGresAttDesc)); + + if (!ad) + { + PQseterror(PQT_OUTOFMEMORY); + return NULL; + } + + tableid = PQftable(args->get.result, args->get.field_num); + columnid = PQftablecol(args->get.result, args->get.field_num); + format = PQfformat(args->get.result, args->get.field_num); + + for (i=0; i < nattrs; i++) + { + ad[i].tableid = tableid; + ad[i].columnid = columnid; + ad[i].format = format; + + /* simple array */ + if (args->typhandler->nattrs == 0) + { + ad[i].typid = args->typhandler->typoid; + ad[i].typlen = args->typhandler->typlen; + ad[i].name = NULL; + ad[i].atttypmod = -1; + } + /* composite/record */ + else + { + ad[i].typid = args->typhandler->attDescs[i].attoid; + ad[i].typlen = args->typhandler->attDescs[i].attlen; + ad[i].name = args->typhandler->attDescs[i].attname; + ad[i].atttypmod = args->typhandler->attDescs[i].atttypmod; + } + } + + res = PQcopyResult(args->get.result, + PG_COPYRES_EVENTS | PG_COPYRES_NOTICEHOOKS); + + if (!res) + { + free(ad); + PQseterror(PQT_OUTOFMEMORY); + return NULL; + } + + if (!PQsetResultAttrs(res, nattrs, ad)) + { + PQclear(res); + PQseterror(PQT_OUTOFMEMORY); + res = NULL; + } + + free(ad); + return res; +} + +#ifdef STRICT_MEMORY_ALIGNMENT +short +pqt_buf_getint2(char *buffer) +{ + short n; + memcpy(&n, buffer, 2); + return (short) ntohs(n); +} + +int +pqt_buf_getint4(char *buffer) +{ + int n; + memcpy(&n, buffer, 4); + return (int) ntohl(n); +} +#endif + +void +pqt_swap8(void *outp, void *inp, int tonet) +{ + static int n = 1; + +#ifdef STRICT_MEMORY_ALIGNMENT + unsigned int in[2]; + unsigned int out[2]; + memcpy(&in, inp, 8); +#else + unsigned int *in = (unsigned int *) inp; + unsigned int *out = (unsigned int *) outp; +#endif + + /* swap when needed */ + if (*(char *)&n == 1) + { + out[0] = (unsigned int) (tonet ? htonl(in[1]) : ntohl(in[1])); + out[1] = (unsigned int) (tonet ? htonl(in[0]) : ntohl(in[0])); + } + else + { + out[0] = in[0]; + out[1] = in[1]; + } + +#ifdef STRICT_MEMORY_ALIGNMENT + memcpy(outp, out, 8); +#endif +} + +int +pqt_text_to_int8(char *val, void *out) +{ + PGint8 n; + + /* NOTE: port version of strtoll in port.c. */ + errno = 0; + if ((n = (PGint8) strtoll(val, NULL, 10)) == 0 && errno) + return -1; + + *(PGint8 *) out = n; + return 0; +} + +int +pqt_text_to_float8(double *f8, char *text, char **endptr) +{ + double d; + + errno = 0; + if ((d = strtod(text, endptr)) == 0 && errno) + return 0; + + *f8 = d; + return 1; +} + +/* Checks buffer and truncates 'src' if 'dest' is too small. */ +char * +pqt_strcpy(char *dest, size_t size, const char *src) +{ + size_t src_len = strlen(src); + + /* truncate if needed */ + if (src_len >= size) + src_len = size - 1; + + memcpy(dest, src, src_len); + dest[src_len] = 0; + return dest; +} + + +/* --------------------------- + * Everything below was taken from postgresql project + * A couple of changes here and there. + */ + +#ifndef HIGHBIT +# define HIGHBIT (0x80) +#endif + +#ifndef IS_HIGHBIT_SET +# define IS_HIGHBIT_SET(ch) ((unsigned char)(ch) & HIGHBIT) +#endif + +/* + * Fold a character to lower case. + * + * Unlike some versions of tolower(), this is safe to apply to characters + * that aren't upper case letters. Note however that the whole thing is + * a bit bogus for multibyte character sets. + */ +unsigned char +pqt_tolower(unsigned char ch) +{ + if (ch >= 'A' && ch <= 'Z') + ch += 'a' - 'A'; + else if (IS_HIGHBIT_SET(ch) && isupper(ch)) + ch = (unsigned char) tolower(ch); + return ch; +} + +/* + * Case-independent comparison of two null-terminated strings. + */ +int +pqt_strcasecmp(const char *s1, const char *s2) +{ + for (;;) + { + unsigned char ch1 = (unsigned char) *s1++; + unsigned char ch2 = (unsigned char) *s2++; + + if (ch1 != ch2) + { + if (ch1 >= 'A' && ch1 <= 'Z') + ch1 += 'a' - 'A'; + else if (IS_HIGHBIT_SET(ch1) && isupper(ch1)) + ch1 = (unsigned char)tolower(ch1); + + if (ch2 >= 'A' && ch2 <= 'Z') + ch2 += 'a' - 'A'; + else if (IS_HIGHBIT_SET(ch2) && isupper(ch2)) + ch2 = (unsigned char)tolower(ch2); + + if (ch1 != ch2) + return (int) ch1 - (int) ch2; + } + if (ch1 == 0) + break; + } + return 0; +} + +/* + * Case-independent comparison of two not-necessarily-null-terminated + * strings. At most n bytes will be examined from each string. + */ +int +pqt_strncasecmp(const char *s1, const char *s2, size_t n) +{ + while (n-- > 0) + { + unsigned char ch1 = (unsigned char) *s1++; + unsigned char ch2 = (unsigned char) *s2++; + + if (ch1 != ch2) + { + if (ch1 >= 'A' && ch1 <= 'Z') + ch1 += 'a' - 'A'; + else if (IS_HIGHBIT_SET(ch1) && isupper(ch1)) + ch1 = (unsigned char)tolower(ch1); + + if (ch2 >= 'A' && ch2 <= 'Z') + ch2 += 'a' - 'A'; + else if (IS_HIGHBIT_SET(ch2) && isupper(ch2)) + ch2 = (unsigned char)tolower(ch2); + + if (ch1 != ch2) + return (int) ch1 - (int) ch2; + } + if (ch1 == 0) + break; + } + return 0; +} + + + diff --git a/source/src/varlena.c b/source/src/varlena.c new file mode 100644 index 0000000..a20727c --- /dev/null +++ b/source/src/varlena.c @@ -0,0 +1,88 @@ + +/* + * varlena.c + * Type handler for the variable length data types. + * + * Copyright (c) 2008-2015 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. + */ + +#include "libpqtypes-int.h" + +/* put a type in its string representation, text format */ +int +pqt_put_str(PGtypeArgs *args) +{ + args->format = TEXTFMT; + args->put.out = va_arg(args->ap, char *); + return args->put.out ? (int)strlen(args->put.out)+1 : 0; +} + +/* varchar, bpchar and name use the text handlers */ +int +pqt_put_text(PGtypeArgs *args) +{ + args->put.out = va_arg(args->ap, PGtext); + return args->put.out ? (int)strlen(args->put.out) : 0; +} + +/* varchar, bpchar and name use the text handlers */ +int +pqt_get_text(PGtypeArgs *args) +{ + DECLVALUE(args); + PGtext *textp = va_arg(args->ap, PGtext *); + CHKGETVALS(args, textp); + *textp = value; + return 0; +} + +int +pqt_put_bytea(PGtypeArgs *args) +{ + PGbytea *bytea = va_arg(args->ap, PGbytea *); + PUTNULLCHK(args, bytea); + args->put.out = bytea->data; + return bytea->len; +} + +int +pqt_get_bytea(PGtypeArgs *args) +{ + DECLVALUE(args); + DECLLENGTH(args); + PGbytea *bytea = va_arg(args->ap, PGbytea *); + + CHKGETVALS(args, bytea); + + if (args->format == TEXTFMT) + { + size_t len = 0; + + value = (char *) PQunescapeBytea((const unsigned char *) value, &len); + if (!value) + RERR_STR2INT(args); + + bytea->data = (char *) PQresultAlloc(args->get.result, len); + if (!bytea->data) + { + PQfreemem(value); + RERR_MEM(args); + } + + bytea->len = (int) len; + memcpy(bytea->data, value, len); + PQfreemem(value); + return 0; + } + + /* binary format */ + bytea->len = valuel; + bytea->data = value; + return 0; +} + + + diff --git a/source/win32.mak b/source/win32.mak new file mode 100644 index 0000000..3a5dd1d --- /dev/null +++ b/source/win32.mak @@ -0,0 +1,66 @@ +############################################################## +# Project: libpqtypes +# Makefile for Microsoft Visual Studio 6, 7 or 8 +# +# nmake -f win32.mak [options] [targets] +# +# For further build instructions, see the package's INSTALL file. +# +# Authors: Andrew Chernow, Merlin Moncure +# Contact: libpqtypes@esilo.com +############################################################## + +PROJNAME = libpqtypes + +OBJECTS = src\array.obj src\datetime.obj src\error.obj \ + src\events.obj src\exec.obj src\geo.obj src\handler.obj \ + src\misc.obj src\network.obj src\numerics.obj \ + src\param.obj src\port.obj src\record.obj src\spec.obj \ + src\utils.obj src\varlena.obj + + +CFLAGS = $(CFLAGS) /nologo /W4 /MD /GF /Ob2 /O2 /Oi- /D_WIN32_WINNT=0x0501 + +!IFDEF MT +CFLAGS = $(CFLAGS) /DPQT_THREAD_SAFE +!ENDIF + +!IFDEF PQT_LONG_LONG +CFLAGS = $(CFLAGS) /DPQT_LONG_LONG=$(PQT_LONG_LONG) +!ENDIF + +# set the libpath for libpq.dll and libpqdll.lib (same thing as gcc -L) +!IFDEF LPATH +LIBPATH = /LIBPATH:$(LPATH) +!ENDIF + +INC2 = $(INC) /Isrc +LIBS = ws2_32.lib libpq.lib +LINKOPTS = /nologo /subsystem:windows /incremental:no +LINKIGNORE = /IGNORE:4089 /IGNORE:4006 /IGNORE:4068 +LIBOPTS = /nologo /subsystem:windows $(LINKIGNORE) + +all: $(OBJECTS) + lib $(LIBOPTS) /OUT:$(PROJNAME).lib $(OBJECTS) + link $(LINKOPTS) /dll $(OBJECTS) /out:$(PROJNAME).dll \ + /IMPLIB:$(PROJNAME)dll.lib $(LIBS) $(LIBPATH) + +test: + $(CC) $(CFLAGS) $(INC2) src\regression-test.c /Feregtest.exe \ + $(LIBS) $(PROJNAME)dll.lib /link $(LIBPATH) $(LINKIGNORE) + -@del regression-test.obj + +$(OBJECTS): + $(CC) $(CFLAGS) $(INC2) /c $** /Fo$*.obj + +clean: + -@del /Q /F \ + $(OBJECTS) \ + $(PROJNAME).dll \ + $(PROJNAME).dll.manifest \ + $(PROJNAME).lib \ + $(PROJNAME)dll.lib \ + $(PROJNAME)dll.exp \ + regtest.exe \ + regtest.exe.manifest 2>NUL 1>NUL + diff --git a/source/win32_d.mak b/source/win32_d.mak new file mode 100644 index 0000000..9e8fbfd --- /dev/null +++ b/source/win32_d.mak @@ -0,0 +1,66 @@ +############################################################## +# Project: libpqtypes +# Makefile for Microsoft Visual Studio 6, 7 or 8 +# +# nmake -f win32.mak [options] [targets] +# +# For further build instructions, see the package's INSTALL file. +# +# Authors: Andrew Chernow, Merlin Moncure +# Contact: libpqtypes@esilo.com +############################################################## + +PROJNAME = libpqtypes + +OBJECTS = src\array.obj src\datetime.obj src\error.obj \ + src\events.obj src\exec.obj src\geo.obj src\handler.obj \ + src\misc.obj src\network.obj src\numerics.obj \ + src\param.obj src\port.obj src\record.obj src\spec.obj \ + src\utils.obj src\varlena.obj + + +CFLAGS = $(CFLAGS) /nologo /W3 /Zi /WX- /Od /MD /GF /Ob2 /Oi- /D_WIN32_WINNT=0x0501 /D "_DEBUG" + +!IFDEF MT +CFLAGS = $(CFLAGS) /DPQT_THREAD_SAFE +!ENDIF + +!IFDEF PQT_LONG_LONG +CFLAGS = $(CFLAGS) /DPQT_LONG_LONG=$(PQT_LONG_LONG) +!ENDIF + +# set the libpath for libpq.dll and libpqdll.lib (same thing as gcc -L) +!IFDEF LPATH +LIBPATH = /LIBPATH:$(LPATH) +!ENDIF + +INC2 = $(INC) /Isrc +LIBS = ws2_32.lib libpq.lib +LINKOPTS = /nologo /subsystem:windows /incremental /DEBUG +LINKIGNORE = /IGNORE:4089 /IGNORE:4006 /IGNORE:4068 +LIBOPTS = /nologo /subsystem:windows $(LINKIGNORE) + +all: $(OBJECTS) + lib $(LIBOPTS) /OUT:$(PROJNAME).lib $(OBJECTS) + link $(LINKOPTS) /dll $(OBJECTS) /out:$(PROJNAME).dll \ + /IMPLIB:$(PROJNAME)dll.lib $(LIBS) $(LIBPATH) + +test: + $(CC) $(CFLAGS) $(INC2) src\regression-test.c /Feregtest.exe \ + $(LIBS) $(PROJNAME)dll.lib /link $(LIBPATH) $(LINKIGNORE) + -@del regression-test.obj + +$(OBJECTS): + $(CC) $(CFLAGS) $(INC2) /c $** /Fo$*.obj + +clean: + -@del /Q /F \ + $(OBJECTS) \ + $(PROJNAME).dll \ + $(PROJNAME).dll.manifest \ + $(PROJNAME).lib \ + $(PROJNAME)dll.lib \ + $(PROJNAME)dll.exp \ + regtest.exe \ + regtest.exe.manifest 2>NUL 1>NUL +