libpqtypes - regression-test.c

Home Page

   1 
   2 #ifdef HAVE_CONFIG_H
   3 # include "pqt_config.h"
   4 #endif
   5 
   6 #define PLN do{ \
   7   printf("%s:%d\n", __FUNCTION__, __LINE__); \
   8   fflush(stdout); \
   9 }while(0)
  10 
  11 #if defined(_WIN32) || defined(_WIN64)
  12 # define _CRT_SECURE_NO_WARNINGS /* shut up msvc8! */
  13 # ifdef _MSC_VER
  14 #   pragma warning (disable : 4127 4706)
  15 # endif
  16 # include <winsock2.h>
  17 # include <ws2tcpip.h> /* getnameinfo */
  18 # ifdef _MSC_VER
  19 #   define I64FMT "%I64d"
  20 # else
  21 #   define I64FMT "%lld"
  22 # endif
  23 #else
  24 # define SIGNAL_SUPPORT
  25 # include <netdb.h>
  26 # include <unistd.h>
  27 # include <sys/socket.h>
  28 # include <netinet/in.h>
  29 # define I64FMT "%lld"
  30 #endif
  31 
  32 #ifdef SIGNAL_SUPPORT
  33 # include <signal.h>
  34 #endif
  35 
  36 #include <libpqtypes.h>
  37 #include <stdlib.h>
  38 #include <string.h>
  39 #include <limits.h>
  40 #include <ctype.h>
  41 #include <time.h>
  42 #include <stdarg.h>
  43 #include <errno.h>
  44 
  45 #ifndef LLONG_MAX
  46 # ifdef LONG_LONG_MAX
  47 #   define LLONG_MAX LONG_LONG_MAX
  48 # else
  49 #   define LLONG_MAX 9223372036854775807LL
  50 # endif
  51 #endif
  52 
  53 #ifndef LLONG_MIN
  54 # ifdef LONG_LONG_MIN
  55 #   define LLONG_MIN LONG_LONG_MIN
  56 # else
  57 #   define LLONG_MIN -9223372036854775808LL
  58 # endif
  59 #endif
  60 
  61 #define RESERR PQresultErrorMessage(result)
  62 #define CLEAR_TABLE(tname) PQclear(PQexec(conn, "TRUNCATE " tname))
  63 #define DROP_TABLE(tname) PQclear(PQexec(conn, "DROP TABLE IF EXISTS " tname))
  64 
  65 #define EXECOKAY(msg, status) do{ \
  66   if (PQresultStatus(result) != (status)) \
  67   { \
  68     fprintf(stderr, "  **ERROR: %s - %s\n", msg, RESERR); \
  69     PQclear(result); \
  70     failcnt++; \
  71     return; \
  72   } \
  73 }while (0)
  74 
  75 #define CMDOKAY(msg) EXECOKAY(msg, PGRES_COMMAND_OK)
  76 #define TUPSOKAY(msg) EXECOKAY(msg, PGRES_TUPLES_OK)
  77 
  78 #define PUTOKAY(paramobj, retval, msg) do{ \
  79   if ((retval) == 0){ \
  80     failcnt++; \
  81     fprintf(stderr, "  **ERROR: %s - %s\n", msg, PQgeterror()); \
  82     return; \
  83   } \
  84 }while (0)
  85 
  86 #define GETOKAY(retval, msg) GETOKAY2(result, retval, msg)
  87 #define GETOKAY2(_res, retval, msg) do{ \
  88   if ((retval) == 0){ \
  89     failcnt++; \
  90     fprintf(stderr, "  **ERROR: %s - %s\n", msg, PQgeterror()); \
  91     PQclear(result); \
  92     return; \
  93   } \
  94 }while (0)
  95 
  96 #define CHKNUM(typspec, getval, putval) do{ \
  97   if ((getval) != (putval)) { \
  98     failcnt++; \
  99     fprintf(stderr, "  %s - FAILED\n", typspec); \
 100   } \
 101   else \
 102     printf("  %s - passed\n", typspec); \
 103   testcnt++; \
 104 } while (0)
 105 
 106 #define CHKGEO(type) do{ \
 107   const char *typspec = "%" # type; \
 108   if (memcmp(& type, & type ## val, sizeof(type))) { \
 109     failcnt++; \
 110     fprintf(stderr, "  %s - FAILED\n", typspec); \
 111   } \
 112   else \
 113     printf("  %s - passed\n", typspec); \
 114   testcnt++; \
 115 } while (0)
 116 
 117 #define CHKVLEN(typ, resval, inval) do{ \
 118   testcnt++; \
 119   if (strcmp(resval, inval)) { \
 120     failcnt++; \
 121     fprintf(stderr, "  %%" typ " - FAILED\n"); \
 122   } \
 123   else \
 124     printf("  %%" typ " - passed\n"); \
 125 }while (0)
 126 
 127 static void cleanup(void);
 128 static void test_natives(int format);
 129 static void test_composite(void);
 130 static void test_geometrics(int format);
 131 static void test_varlen(int format);
 132 static void test_datetime(int format);
 133 static void test_network(int format);
 134 static int epoch_put(PGtypeArgs *args);
 135 static int epoch_get(PGtypeArgs *args);
 136 static void test_subclass(int format);
 137 static PGresult *execf(const char *cmdSpec, ...);
 138 static int bpcharcmp(const char *put, const char *get, int charlen);
 139 
 140 #ifdef SIGNAL_SUPPORT
 141 static void sighandler(int s);
 142 #endif
 143 
 144 static void _notice_processor(void *arg, const char *message)
 145 {/* do nothing */
 146   arg = NULL;
 147   message = NULL;
 148 }
 149 
 150 
 151 static int testcnt = 0;
 152 static int failcnt = 0;
 153 static const char *datestyle = "";
 154 static PGconn *conn = NULL;
 155 static PGparam *param = NULL;
 156 static PGresult *result = NULL;
 157 
 158 #define ALIGN_NUM(val, mult) (((val) + ((mult) - 1)) & ~(((mult) - 1)))
 159 
 160 int main(int argc, char **argv)
 161 {
 162   PGint4 n;
 163   PGregisterType regtype;
 164   char *conninfo = argc > 1 ? argv[1] : "hostaddr=127.0.0.1 user=postgres";
 165 
 166   conn = PQconnectdb(conninfo);
 167   if (PQstatus(conn) != CONNECTION_OK)
 168   {
 169     fprintf(stderr, "connection failure: %s\n", PQerrorMessage(conn));
 170     return 1;
 171   }
 172 
 173   PQsetNoticeProcessor(conn, _notice_processor, NULL);
 174 
 175   if (!PQtypesRegister(conn))
 176   {
 177     fprintf(stderr, "Failed to register libpqtypes as a libpq event proc\n");
 178     PQfinish(conn);
 179     return 1;
 180   }
 181 
 182   testcnt++;
 183   regtype.typname = "bad_type";
 184   if(PQregisterComposites(conn, &regtype, 1))
 185   {
 186     failcnt++;
 187     fprintf(stderr, "Test bad type register failure case (FAILED)\n");
 188   }
 189 
 190   regtype.typname = "mytype_t";
 191   if(!PQregisterComposites(conn, &regtype, 1))
 192     fprintf(stderr, "%s\n", PQgeterror());
 193 
 194   test_composite();
 195 
 196   PQclear(PQexec(conn, "SET DateStyle TO 'ISO'"));
 197   param = PQparamCreate(conn);
 198 
 199   /* test PQputvf - result should be 10 */
 200   testcnt++;
 201   printf("\nPQputvf\n");
 202   result = execf("SELECT %int4 + (%int4 % %int4) AS answer", 9, 67, 3);
 203   if (PQresultStatus(result) != PGRES_TUPLES_OK)
 204   {
 205     fprintf(stderr, "  **ERROR: PQputvf - %s\n", PQgeterror());
 206     PQclear(result);
 207     failcnt++;
 208     return 1;
 209   }
 210 
 211   PQgetf(result, 0, "#int4", "answer", &n);
 212   PQclear(result);
 213   if (n != 10)
 214   {
 215     failcnt++;
 216     fprintf(stderr, "  PQputvf - FAILED\n");
 217   }
 218   else
 219   {
 220     printf("  PQputvf - passed\n");
 221   }
 222 
 223   testcnt++;
 224   printf("\nPQspecPrepare\n");
 225   if(!PQspecPrepare(conn, "test", "SELECT %int8 + %int4", 1))
 226   {
 227     failcnt++;
 228     fprintf(stderr, "  PQspecPrepare - FAILED (%s)\n", PQgeterror());
 229   }
 230   else
 231   {
 232     char data[4];
 233     PGbytea ba;
 234     PGint8 i8 = 45;
 235     ba.len = 4;
 236     ba.data = data;
 237 
 238     result = PQexecf(conn, "@test", i8, 4);
 239     if(!result)
 240     {
 241       failcnt++;
 242       fprintf(stderr, "  PQspecPrepare - FAILED (%s)\n", PQgeterror());
 243     }
 244     else
 245     {
 246       PGint8 i8out=0;
 247 
 248       PQgetf(result, 0, "%int8", 0, &i8out);
 249 
 250       if(i8 + 4 != i8out)
 251       {
 252         failcnt++;
 253         fprintf(stderr, "  PQspecPrepare - FAILED (expected %lld but got %lld)\n",
 254           i8+4, i8out);
 255       }
 256       else
 257       {
 258         printf("  PQspecPrepare - passed\n");
 259       }
 260 
 261       PQclear(result);
 262     }
 263   }
 264 
 265   datestyle = PQparameterStatus(conn, "DateStyle");
 266   if (!datestyle)
 267     datestyle = "";
 268 
 269 #ifdef SIGNAL_SUPPORT
 270   signal(SIGINT, sighandler);
 271   signal(SIGILL, sighandler);
 272   signal(SIGSEGV, sighandler);
 273 # ifdef SIGFPE
 274   signal(SIGFPE, sighandler);
 275 # endif
 276 # ifdef SIGBUS
 277   signal(SIGBUS, sighandler);
 278 # endif
 279 #endif
 280 
 281 
 282   test_natives(0);
 283   test_natives(1);
 284   test_varlen(0);
 285   test_varlen(1);
 286   test_geometrics(0);
 287   test_geometrics(1);
 288   test_network(0);
 289   test_network(1);
 290 
 291   regtype.typname = "libpq.epoch=pg_catalog.timestamptz";
 292   regtype.typput = epoch_put;
 293   regtype.typget = epoch_get;
 294 
 295   /* Test type sub-classing, use the epoch example from docs */
 296   if (!PQregisterSubClasses(conn, &regtype, 1))
 297   {
 298     fprintf(stderr, "PQregisterSubClasses(epoch): %s\n", PQerrorMessage(conn));
 299   }
 300   else
 301   {
 302     test_subclass(0);
 303     test_subclass(1);
 304   }
 305 
 306   test_datetime(0);
 307   test_datetime(1);
 308 
 309   /* datestyle is cached in PGparam.  Since we need to change the datestyle,
 310    * we destroy the current param and recreate it later.
 311    */
 312   PQparamClear(param);
 313 
 314   /* Just tested ISO, test a non-ISO */
 315   PQclear(PQexec(conn, "SET DateStyle TO 'POSTGRES'"));
 316   datestyle = PQparameterStatus(conn, "DateStyle");
 317   if (!datestyle)
 318     datestyle = "";
 319 
 320   param = PQparamCreate(conn);
 321   test_datetime(0);
 322   test_datetime(1);
 323   cleanup();
 324 
 325   printf("\nRan %d tests - %d failed\n", testcnt, failcnt);
 326   return 1;
 327 }
 328 
 329 /* Calls putf and executes cmdSpec. Returns a result object */
 330 static PGresult *execf(const char *cmdSpec, ...)
 331 {
 332   int n;
 333   PGresult *res;
 334   va_list ap;
 335   char stmt[4096];
 336   PGparam *prm = PQparamCreate(conn);
 337 
 338   va_start(ap, cmdSpec);
 339   n = PQputvf(prm, stmt, sizeof(stmt), cmdSpec, ap);
 340   va_end(ap);
 341 
 342   /* error message in PQparamErrorMessage */
 343   if (!n)
 344   {
 345     PQparamClear(prm);
 346     return NULL;
 347   }
 348 
 349   printf("  %s\n", stmt);
 350   res = PQparamExec(conn, prm, stmt, 1);
 351   PQparamClear(prm);
 352   return res;
 353 }
 354 
 355 /* Handle BPCHAR (Blank-Padded) SQL-type "character(n)" */
 356 static int bpcharcmp(const char *put, const char *get, int charlen)
 357 {
 358   int i;
 359   int len = (int)strlen(put);
 360 
 361   for (i=len; i < charlen; i++)
 362     if (!isspace(get[i]))
 363       return 1;
 364 
 365   return memcmp(put, get, len);
 366 }
 367 
 368 static void cleanup(void)
 369 {
 370   DROP_TABLE("libpq_natives");
 371   DROP_TABLE("libpq_varlen");
 372   DROP_TABLE("libpq_geos");
 373   DROP_TABLE("libpq_network");
 374   DROP_TABLE("libpq_datetime");
 375   DROP_TABLE("libpq_subclass");
 376   DROP_TABLE("libpq_composite");
 377   PQclear(PQexec(conn, "DROP TYPE complex"));
 378   PQclear(PQexec(conn, "DROP TYPE simple"));
 379   PQparamClear(param);
 380   PQfinish(conn);
 381 }
 382 
 383 /* make an effort to clean up */
 384 #ifdef SIGNAL_SUPPORT
 385 static void sighandler(int s)
 386 {
 387   cleanup();
 388   fprintf(stderr, "\n**Caught signal: %d ... aborting\n", s);
 389   exit(1);
 390 }
 391 #endif
 392 
 393 static void test_composite(void)
 394 {
 395   int i,x,r;
 396   int ntups;
 397   int inttups;
 398   PGtext text;
 399   PGparam *simple;
 400   PGparam *complex;
 401   PGparam *prm;
 402   PGresult *simple_res;
 403   PGarray intarr;
 404   PGarray comparr;
 405   PGchar buf[64];
 406   PGint4 a;
 407   PGint8 b;
 408   PGregisterType types[] = {
 409     {"public.simple", NULL, NULL},
 410     {"public.complex", NULL, NULL}
 411   };
 412 
 413   printf("\nComposites & Arrays: (always binary)\n");
 414 
 415   printf("  Testing empty array handling ");
 416   PQclear(PQexec(conn, "DROP TABLE libpq_array"));
 417   result = PQexec(conn, "CREATE TABLE libpq_array (arr int[])");
 418   CMDOKAY("creating libpq_array table:");
 419   PQclear(result);
 420 
 421   result = PQexec(conn, "INSERT INTO libpq_array VALUES ('{}')");
 422   CMDOKAY("inserting into libpq_array:");
 423   PQclear(result);
 424 
 425   result = PQparamExec(conn, NULL, "SELECT * FROM libpq_array", 1);
 426   TUPSOKAY("executing select on libpq_array:");
 427 
 428   r = PQgetf(result, 0, "%int4[]", 0, &intarr);
 429 
 430   GETOKAY(r, "Failed to get an empty array");
 431   PQclear(result);
 432 
 433   /* both values should be 0 */
 434   testcnt++;
 435   if(PQntuples(intarr.res) != 0 || intarr.ndims != 0)
 436   {
 437     fprintf(stderr, "- FAILED\n    ***ERROR: tuple and dimension count should "
 438       "both be zero - ntups=%d, ndims=%d\n",
 439       PQntuples(intarr.res), intarr.ndims);
 440     failcnt++;
 441   }
 442   else
 443   {
 444     printf("- passed\n");
 445   }
 446   PQclear(intarr.res);
 447 
 448   DROP_TABLE("libpq_composite");
 449   PQclear(PQexec(conn, "DROP TYPE public.complex"));
 450 
 451   PQclear(PQexec(conn, "DROP TYPE public.simple"));
 452   result = PQexec(conn, "CREATE TYPE simple AS (a int4, b int8)");
 453   CMDOKAY("creating simple type");
 454   PQclear(result);
 455 
 456   result = PQexec(conn, "CREATE TYPE complex AS (t text, s simple, arr int4[][])");
 457   CMDOKAY("creating complex type");
 458   PQclear(result);
 459 
 460   if (!PQregisterComposites(conn, types, 2))
 461   {
 462     fprintf(stderr, "  **ERROR: PQregisterComposites(%s %s): %s\n",
 463       types[0].typname, types[1].typname, PQgeterror());
 464     failcnt++;
 465     return;
 466   }
 467 
 468   result = PQexec(conn, "CREATE TABLE libpq_composite (comparr complex[])");
 469   CMDOKAY("creating libpq_composite table:");
 470   PQclear(result);
 471 
 472   simple = PQparamCreate(conn);
 473   complex = PQparamCreate(conn);
 474   prm = PQparamCreate(conn);
 475 
 476   /* array[10][3] */
 477   intarr.ndims     = 2;
 478   intarr.dims[0]   = 10;
 479   intarr.dims[1]   = 3;
 480   intarr.lbound[0] = 1;
 481   intarr.lbound[1] = 1;
 482   intarr.param = PQparamCreate(conn);
 483 
 484   /* If a 1D array is being used, you can avoid setting the dimension
 485    * members of the PGarray structure by zeroing it.
 486    */
 487   memset(&comparr, 0, sizeof(PGarray));
 488   comparr.param = PQparamCreate(conn);
 489 
 490   /* generate a complex[] containing 100 elements */
 491   for (i=0; i < 100; i++)
 492   {
 493     PGint8 val = ((PGint8)1<<48) + (PGint8)i;
 494 
 495     /* pack the fields of the 'simple' composite */
 496     r = PQputf(simple, "%int4 %int8", i+1000000, val);
 497     PUTOKAY(simple, r, "putting simple attributes");
 498 
 499     /* populate the complex.arr[10][3] */
 500     PQputf(intarr.param, "%null");
 501     for (x=1; x < 30; x++)
 502     {
 503       r= PQputf(intarr.param, "%int4", x+1);
 504       PUTOKAY(intarr.param, r, "putting complex.arr element");
 505     }
 506 
 507     /* pack the fields of the 'complex' composite */
 508     sprintf((char *) buf, "text_%03d", i+1);
 509     r = PQputf(complex, "%text %simple %int4[]", buf, simple, &intarr);
 510     PUTOKAY(complex, r, "putting complex attributes");
 511 
 512     /* now put element 'i' into the composite array */
 513     r = PQputf(comparr.param, "%complex", complex);
 514     PUTOKAY(comparr.param, r, "putting complex[] element");
 515 
 516     /* make sure to reset the params, otherwise you will continue to
 517      * append parameters to below params.  DO NOT reset the comparr.param,
 518      * as this is the array we want to insert.
 519      */
 520     PQparamReset(simple);
 521     PQparamReset(intarr.param);
 522     PQparamReset(complex);
 523 
 524     /* sneak in a NULL element.  NULL composite array items
 525      * used to be a bug.
 526      */
 527     if(i == 49)
 528     {
 529       PQputf(comparr.param, "%complex", NULL);
 530       i++;
 531     }
 532   }
 533 
 534   /* insert the complex[] */
 535   PQputf(prm, "%complex[]", &comparr);
 536   result = PQparamExec(conn, prm,
 537     "INSERT INTO libpq_composite VALUES ($1)", 1);
 538   CMDOKAY("inserting into libpq_composite");
 539   PQclear(result);
 540 
 541   /* clear all params (order doesn't matter, they are isolated structures) */
 542   PQparamClear(simple);
 543   PQparamClear(intarr.param);
 544   PQparamClear(complex);
 545   PQparamClear(comparr.param);
 546   PQparamClear(prm);
 547 
 548   /* select complex[] back out */
 549   result = PQparamExec(conn, NULL, "SELECT * FROM libpq_composite", 1);
 550   TUPSOKAY("PQparamExec(SELECT:composite[])");
 551 
 552   /* getf it into a PGarray */
 553   r = PQgetf(result, 0, "%complex[]", 0, &comparr);
 554   GETOKAY(r, "PQgetf(complex[])");
 555   PQclear(result);
 556 
 557   /* composite arrays come back 1 tuple per element and 1 field per attribute */
 558   ntups = PQntuples(comparr.res);
 559   if (ntups != 100)
 560   {
 561     failcnt++;
 562     fprintf(stderr, "put 100 elements into complex[] but got back %d\n", ntups);
 563     PQclear(comparr.res);
 564     return;
 565   }
 566 
 567   testcnt++;
 568   for (i=0; i < ntups; i++)
 569   {
 570     /* Verify element 50 is NULL */
 571     if(i==50)
 572     {
 573       /* All fields should be NULL */
 574       if(!PQgetisnull(comparr.res, i, 0) ||
 575          !PQgetisnull(comparr.res, i, 1) ||
 576          !PQgetisnull(comparr.res, i, 2))
 577       {
 578         failcnt++;
 579         fprintf(stderr, "  Element 50 should of been NULL\n");
 580         PQclear(comparr.res);
 581         return;
 582       }
 583 
 584       continue;
 585     }
 586 
 587     /* get the complex composite at tuple 'i' */
 588     r = PQgetf(comparr.res, i, "%text %simple #int4[]",
 589       0, &text, 1, &simple_res, "arr", &intarr);
 590     GETOKAY2(comparr.res, r, "PQgetf(comparr.res element)");
 591 
 592     /* check the text field */
 593     sprintf((char *) buf, "text_%03d", i+1);
 594     if (strcmp(text, (char *) buf))
 595     {
 596       failcnt++;
 597       fprintf(stderr, "complex[%d].t failed: sent=%s, recv=%s\n", i, buf, text);
 598       PQclear(simple_res);
 599       PQclear(intarr.res);
 600       PQclear(comparr.res);
 601       return;
 602     }
 603 
 604     /* Check the simple field: reference by fname, use '#' rather than '%' */
 605     r = PQgetf(simple_res, 0, "#int4 #int8", "a", &a, "b", &b);
 606     GETOKAY2(simple_res, r, "PQgetf(complex[].simple.*)");
 607     PQclear(simple_res);
 608 
 609     /* verify simple.* values */
 610     if (a != (i+1000000) || b != ((PGint8) 1<<48)+i)
 611     {
 612       failcnt++;
 613       fprintf(stderr, "complex[%d].simple.* failed: "
 614         "sent(a=%d, b=%lld), recv(a=%d, b=%lld)\n",
 615         i, i+1000000, ((PGint8) 1<<48)+i, a, b);
 616       PQclear(intarr.res);
 617       PQclear(comparr.res);
 618       return;
 619     }
 620 
 621     /* check the int[10][3] field */
 622     if (intarr.dims[0] != 10 || intarr.dims[1] != 3 ||
 623        intarr.lbound[0] != 1 || intarr.lbound[1] != 1 || intarr.ndims != 2)
 624     {
 625       failcnt++;
 626       fprintf(stderr, "complex[%d].int[][] dimension info failed: "
 627         "sent(ndims=2, arr[1:10][1:3]), recv(ndims=%d, arr[%d:%d][%d:%d])\n",
 628         i, intarr.ndims, intarr.lbound[0], intarr.dims[0],
 629         intarr.lbound[1], intarr.dims[1]);
 630       PQclear(intarr.res);
 631       PQclear(comparr.res);
 632       return;
 633     }
 634 
 635     inttups = PQntuples(intarr.res);
 636     for (x=1; x < inttups; x++)
 637     {
 638       r = PQgetf(intarr.res, x, "%int4", 0, &a);
 639       GETOKAY2(intarr.res, r, "PQgetf(complex.int[][] element");
 640 
 641       if (a != x+1)
 642       {
 643         failcnt++;
 644         fprintf(stderr, "  **ERROR: complex[%d].int4[][] sent=%d, recv=%d\n",
 645           i, x+1, a);
 646         PQclear(intarr.res);
 647         PQclear(comparr.res);
 648         return;
 649       }
 650     }
 651 
 652     PQclear(intarr.res);
 653   }
 654 
 655   PQclear(comparr.res);
 656   printf("  composite[] with nested composite and arrays passed\n");
 657 }
 658 
 659 static void test_natives(int format)
 660 {
 661   int r;
 662   PGchar ca;
 663   PGchar cb;
 664   PGint2 i2a, i2b;
 665   PGint4 i4a, i4b;
 666   PGint8 i8a, i8b;
 667   PGfloat4 f4a, f4b;
 668   PGfloat8 f8a, f8b;
 669   PGmoney mona, monb;
 670   Oid oida, oidb;
 671   PGbool boola, boolb;
 672 
 673   PQparamReset(param);
 674   printf("\nNative C types: (%s)\n", format ? "binary" : "text");
 675 
 676   r = PQputf(param, "%char %char %int2 %int2 %int4 %int4 %int8 %int8 "
 677     "%float4 %float4 %float8 %float8 %money %money %oid %oid %bool %bool",
 678     -117, 224, -32000, 32000, INT_MIN, INT_MAX, LLONG_MIN, LLONG_MAX,
 679     -1.23456f, 1.23456f, -123456789.654321, 123456789.654321,
 680     (PGmoney)INT_MIN, 600000000054LL, 91982, 3000000000U, 0, 1);
 681   PUTOKAY(param, r, "PQputf(natives)");
 682 
 683   DROP_TABLE("libpq_natives");
 684 
 685   result = PQexec(conn, "CREATE TABLE libpq_natives ("
 686     "char_a \"char\", char_b \"char\", "
 687     "i2_a int2, i2_b int2, "
 688     "i4_a int4, i4_b int4, "
 689     "i8_a int8, i8_b int8, "
 690     "f4_a float4, f4_b float4, "
 691     "f8_a float8, f8_b float8, "
 692     "mon_a money, mon_b money, "
 693     "oid_a oid, oid_b oid, "
 694     "bool_a bool, bool_b bool)");
 695   CMDOKAY("creating libpq_natives table");
 696   PQclear(result);
 697 
 698   result = PQparamExec(conn, param, "INSERT INTO libpq_natives VALUES"
 699     "($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18)", format);
 700   CMDOKAY("PQparamExec(INSERT:natives)");
 701   PQclear(result);
 702 
 703   result = PQparamExec(conn, NULL, "SELECT * FROM libpq_natives", format);
 704   TUPSOKAY("PQparamExec(SELECT:natives)");
 705 
 706   r = PQgetf(result, 0, "%char %char %int2 %int2 %int4 %int4 %int8 %int8 "
 707     "%float4 %float4 %float8 %float8 %money, %money %oid %oid %bool %bool",
 708     0, &ca, 1, &cb, 2, &i2a, 3, &i2b, 4, &i4a, 5, &i4b, 6, &i8a, 7, &i8b, 8,
 709     &f4a, 9, &f4b, 10, &f8a, 11, &f8b, 12, &mona, 13, &monb, 14, &oida,
 710     15, &oidb, 16, &boola, 17, &boolb);
 711   GETOKAY(r, "PQgetf(natives)");
 712   PQclear(result);
 713 
 714   CHKNUM("%char",   ca,    -117);
 715   CHKNUM("%char",   (unsigned char) cb,    224);
 716   CHKNUM("%int2",   i2a,   -32000);
 717   CHKNUM("%int2",   i2b,   32000);
 718   CHKNUM("%int4",   i4a,   INT_MIN);
 719   CHKNUM("%int4",   i4b,   INT_MAX);
 720   CHKNUM("%int8",   i8a,   LLONG_MIN);
 721   CHKNUM("%int8",   i8b,   LLONG_MAX);
 722   CHKNUM("%float4", f4a,   -1.23456f);
 723   CHKNUM("%float4", f4b,   1.23456f);
 724   CHKNUM("%float8", f8a,   -123456789.654321);
 725   CHKNUM("%float8", f8b,   123456789.654321);
 726   CHKNUM("%money",  mona,  (PGmoney) INT_MIN);
 727   CHKNUM("%money",  monb,  600000000054LL);
 728   CHKNUM("%oid",    oida,  91982);
 729   CHKNUM("%oid",    oidb,  3000000000U);
 730   CHKNUM("%bool",   boola, 0);
 731   CHKNUM("%bool",   boolb, 1);
 732 }
 733 
 734 static void test_geometrics(int format)
 735 {
 736   int r;
 737   static PGpoint ptarr[6] = {{565677.342, 999112.33344},
 738     {-565677.342, -999112.33344}, {-565677.342, 999112.33344},
 739     {565677.342, 999112.33344}, {-565677.342, -999112.33344},
 740     {-565677.342, 999112.33344}};
 741   PGpoint point = {0};
 742   PGpoint pointval = {565677.342, 999112.33344};
 743   PGlseg lseg = {{{0}}};
 744   PGlseg lsegval = {{{565677.342, -999112.33344}, {88876.2314, 77283.323}}};
 745   PGbox box = {{0}};
 746   PGbox boxval = {{565677.342, 88876.2314}, {77283.323, -999112.33344}};
 747   PGcircle circle = {{0,0},0};
 748   PGcircle circleval = {{7765.435, -7743.2993}, 23384.102};
 749   PGpath path = {0, 0, NULL};
 750   PGpath pathval = {6, 1, ptarr};
 751   PGpolygon polygon = {0, NULL};
 752   PGpolygon polygonval = {6, ptarr};
 753 
 754   PQparamReset(param);
 755   printf("\nGeometric types: (%s)\n", format ? "binary" : "text");
 756 
 757   r = PQputf(param, "%point %lseg %box %circle %path %polygon",
 758     &pointval, &lsegval, &boxval, &circleval, &pathval, &polygonval);
 759   PUTOKAY(param, r, "PQputf(geos)");
 760 
 761   DROP_TABLE("libpq_geos");
 762 
 763   result = PQexec(conn, "CREATE TABLE libpq_geos ("
 764     "p point, l lseg, b box, c circle, pa path, poly polygon)");
 765   CMDOKAY("creating libpq_geos table");
 766   PQclear(result);
 767 
 768   result = PQparamExec(conn, param, "INSERT INTO libpq_geos VALUES"
 769     "($1,$2,$3,$4,$5,$6)", format);
 770   CMDOKAY("PQparamExec(INSERT:geos)");
 771   PQclear(result);
 772 
 773   result = PQparamExec(conn, NULL, "SELECT * FROM libpq_geos", format);
 774   TUPSOKAY("PQparamExec(SELECT:geos)");
 775 
 776   r = PQgetf(result, 0, "%point %lseg %box %circle %path %polygon",
 777     0, &point, 1, &lseg, 2, &box, 3, &circle, 4, &path, 5, &polygon);
 778   GETOKAY(r, "PQgetf(geos)");
 779 
 780   CHKGEO(point);
 781   CHKGEO(lseg);
 782   CHKGEO(box);
 783   CHKGEO(circle);
 784 
 785   testcnt++;
 786   if (path.closed == pathval.closed && path.npts == pathval.npts &&
 787      memcmp(path.pts, pathval.pts, path.npts * sizeof(PGpoint))==0)
 788     printf("  %%path - passed\n");
 789   else
 790   {
 791     failcnt++;
 792     fprintf(stderr, "  %%path - FAILED\n");
 793   }
 794 
 795   testcnt++;
 796   if (polygon.npts == polygon.npts &&
 797      memcmp(polygon.pts, polygon.pts, polygon.npts * sizeof(PGpoint))==0)
 798     printf("  %%polygon - passed\n");
 799   else
 800   {
 801     failcnt++;
 802     fprintf(stderr, "  %%polygon - FAILED\n");
 803   }
 804 
 805   /* NOTE: this will invalidate path.pts and polygon.pts */
 806   PQclear(result);
 807 }
 808 
 809 /*
 810  * numeric is tested here because it is always exposed as a string.
 811  * uuid is not a varlen data type, but testing it here is ok
 812  */
 813 static void test_varlen(int format)
 814 {
 815   int r;
 816   static char byteadata[] = {0, 1, 2, 3, 4, 5, 6, 7};
 817   PGbpchar bpcharp=NULL;
 818   PGbpchar bpcharin = "character(n) blank-padded";
 819   PGvarchar varcharp=NULL;
 820   PGvarchar varcharin = "character varying(n)";
 821   PGtext textp=NULL;
 822   PGtext textin = "variable unlimited length";
 823   PGbytea byteap;
 824   PGbytea byteain = {8, byteadata};
 825   PGuuid uuid;
 826   char uuidin[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
 827   PGnumeric num;
 828   PGnumeric numin = "-62731893541288039212143296120112.12431212671229121291821928918211";
 829 
 830   PQparamReset(param);
 831   printf("\nVariable-length types: (%s)\n", format ? "binary" : "text");
 832 
 833   r = PQputf(param, "%bpchar %bpchar* %varchar %varchar* "
 834     "%text %text* %bytea %bytea* %uuid %numeric",
 835     bpcharin, bpcharin, varcharin, varcharin, textin, textin,
 836     &byteain, &byteain, uuidin, numin);
 837   PUTOKAY(param, r, "PQputf(varlen)");
 838 
 839   DROP_TABLE("libpq_varlen");
 840 
 841   result = PQexec(conn, "CREATE TABLE libpq_varlen ("
 842     "bp_a bpchar(32), bp_b bpchar(32), vc_a varchar(32), vc_b varchar(32), "
 843     "text_a text, text_b text, bytea_a bytea, bytea_b bytea, uid uuid, n numeric)");
 844   CMDOKAY("creating libpq_varlen table");
 845   PQclear(result);
 846 
 847   result = PQparamExec(conn, param, "INSERT INTO libpq_varlen VALUES"
 848     "($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)", format);
 849   CMDOKAY("PQparamExec(INSERT:varlen)");
 850   PQclear(result);
 851 
 852   result = PQparamExec(conn, NULL,
 853     "SELECT bp_a,vc_a,text_a,bytea_a,uid,n FROM libpq_varlen", format);
 854   TUPSOKAY("PQparamExec(SELECT:varlen)");
 855 
 856   r = PQgetf(result, 0,
 857     "%bpchar %varchar %text %bytea %uuid %numeric",
 858     0, &bpcharp,     /* field_num, PGbpchar* */
 859     1, &varcharp,    /* field_num, PGvarchar* */
 860     2, &textp,       /* field_num, PGtext* */
 861     3, &byteap,      /* field_num, PGbytea* */
 862     4, &uuid,        /* field_num, PGuuid* */
 863     5, &num);        /* field_num, PGnumeric* */
 864   GETOKAY(r, "PQgetf(varlen)");
 865 
 866   /* Because we are using the '*' specifier flag, we clear the results at
 867    * the end.  '*' flag is just a pointer to the result: like PQgetvalue().
 868    */
 869 
 870   testcnt++;
 871   if (bpcharcmp(bpcharin, bpcharp, 32))
 872   {
 873     failcnt++;
 874     fprintf(stderr, "  %%bpchar - FAILED\n");
 875   }
 876   else
 877     printf("  %%bpchar - passed\n");
 878 
 879   CHKVLEN("varchar",  varcharp, varcharin);
 880   CHKVLEN("text",     textp, textin);
 881 
 882   testcnt++;
 883   if (byteain.len != byteap.len ||
 884      memcmp(byteap.data, byteain.data, byteap.len))
 885   {
 886     failcnt++;
 887     fprintf(stderr, "  %%bytea* - FAILED\n");
 888   }
 889   else
 890     printf("  %%bytea* - passed\n");
 891 
 892   testcnt++;
 893   if (memcmp(uuid, uuidin, 16))
 894   {
 895     failcnt++;
 896     fprintf(stderr, "  %%uuid - FAILED\n");
 897   }
 898   else
 899     printf("  %%uuid - passed\n");
 900 
 901   CHKVLEN("numeric", num, numin);
 902   PQclear(result);
 903 }
 904 
 905 static void test_datetime(int format)
 906 {
 907   int r;
 908   time_t t;
 909   struct tm *tm;
 910   char *tzn;
 911   PGdate date[3] = {{0}};
 912   static PGdate dateval[] = {
 913     {0, 1476,  9,  1, 2260432, 274, 0},         /* 1476-10-01 */
 914     {1, 1209,  7, 17, 1280076, 229, 1},         /* 1209-08-17 BC */
 915     {0, 2007, 11, 23, 2454458, 356, 0}};        /* 2007-12-23 */
 916   PGtime tim[3] = {{0}};
 917   static PGtime timeval[] = {
 918     {23, 10, 31,   7214, 0, -1, 0, {0}},        /* 23:10:31.007214 */
 919     { 2, 19, 52,      0, 0, -1, 0, {0}},        /* 02:19:52 */
 920     {15, 57,  7, 881542, 0, -1, 0, {0}}};       /* 15:57:07.881542 */
 921   PGtime timetz[3] = {{0}};
 922   PGtime timetzval[] = {
 923     {23, 10, 31,   9888, 1, -1, -28800, {0}},   /* 23:10:31.009888-08 */
 924     { 2, 19, 52,      0, 1, -1,   7200, {0}},   /* 02:19:52+02 */
 925     {15, 57,  7, 881542, 1, -1, -10800, {0}}};  /* 15:57:07.881542-03 */
 926   PGtimestamp timestamp[3] = {{0, {0}, {0}}};
 927   PGtimestamp timestampval[] = {
 928     { -15565394969, {0, 1476,  9,  1, 2260432, 274, 0}, {23, 10, 31,   7214, 0, -1, 0, {0}}},   /* 1476-10-01 23:10:31.007214 */
 929     {-100268228408, {1, 1209,  7, 17, 1280076, 229, 1}, { 2, 19, 52,      0, 0, -1, 0, {0}}},   /* 1209-08-17 02:19:52 BC */
 930     {   1198425427, {0, 2007, 11, 23, 2454458, 356, 0}, {15, 57,  7, 881542, 0, -1, 0, {0}}}};  /* 2007-12-23 15:57:07.881542 */
 931   PGinterval interval = {0};
 932   PGinterval intervalval =
 933     {2354, 8, 23, 14, 54, 27, 29333};  /* 2,354 years 8 mons 23 days 14:54:27.029333 */
 934   PGtimestamp timestamptz;
 935   PGtimestamp timestamptzval;
 936 
 937   PQparamReset(param);
 938   memset(&timestamptz, 0, sizeof(PGtimestamp));
 939   memset(&timestamptzval, 0, sizeof(PGtimestamp));
 940 
 941   /* Jan 1, 1980 (returned: text=server time zone, binary=localtime) */
 942   t = 315532800;
 943   tm = localtime(&t);
 944   timestamptzval.epoch       = t;
 945   timestamptzval.time.hour   = tm->tm_hour;
 946   timestamptzval.time.min    = tm->tm_min;
 947   timestamptzval.time.sec    = tm->tm_sec;
 948   timestamptzval.time.usec   = 0;
 949   timestamptzval.time.withtz = 1;
 950   timestamptzval.date.isbc   = 0;
 951   timestamptzval.date.year   = tm->tm_year + 1900;
 952   timestamptzval.date.mon    = tm->tm_mon;
 953   timestamptzval.date.mday   = tm->tm_mday;
 954   PQlocalTZInfo(&t, &timestamptzval.time.gmtoff,
 955     &timestamptzval.time.isdst, &tzn);
 956   strcpy(timestamptzval.time.tzabbr, tzn);
 957 
 958   strcpy(timetzval[0].tzabbr, "GMT-0800");
 959   strcpy(timetzval[1].tzabbr, "GMT+0200");
 960   strcpy(timetzval[2].tzabbr, "GMT-0300");
 961 
 962   printf("\nDate & Time types: (%s '%s')\n", format ? "binary" : "text", datestyle);
 963 
 964   r = PQputf(param, "%date %date %date %time %time %time %timetz %timetz %timetz "
 965     "%timestamp %timestamp %timestamp %interval %timestamptz",
 966     &dateval[0], &dateval[1], &dateval[2], &timeval[0], &timeval[1], &timeval[2],
 967     &timetzval[0], &timetzval[1], &timetzval[2], &timestampval[0], &timestampval[1],
 968     &timestampval[2], &intervalval, &timestamptzval);
 969   PUTOKAY(param, r, "PQputf(datetime)");
 970 
 971   DROP_TABLE("libpq_datetime");
 972 
 973   result = PQexec(conn, "CREATE TABLE libpq_datetime ("
 974     "date_a date, date_b date, date_c date, "
 975     "time_a time, time_b time, time_c time, "
 976     "timetz_a timetz, timetz_b timetz, timetz_c timetz, "
 977     "timestamp_a timestamp, timestamp_b timestamp, timestamp_c timestamp, "
 978     "intvl interval, tstz timestamptz)");
 979   CMDOKAY("creating libpq_datetime table");
 980   PQclear(result);
 981 
 982   result = PQparamExec(conn, param, "INSERT INTO libpq_datetime VALUES"
 983     "($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)", format);
 984   CMDOKAY("PQparamExec(INSERT:datetime)");
 985   PQclear(result);
 986 
 987   result = PQparamExec(conn, NULL, "SELECT * FROM libpq_datetime", format);
 988   TUPSOKAY("PQparamExec(SELECT:datetime)");
 989 
 990   r = PQgetf(result, 0, "%date %date %date %time %time %time %timetz %timetz "
 991     "%timetz %timestamp %timestamp %timestamp %interval %timestamptz",
 992      0, &date[0],       1, &date[1],       2, &date[2],
 993      3, &tim[0],        4, &tim[1],        5, &tim[2],
 994      6, &timetz[0],     7, &timetz[1],     8, &timetz[2],
 995      9, &timestamp[0], 10, &timestamp[1], 11, &timestamp[2],
 996     12, &interval,     13, &timestamptz);
 997   GETOKAY(r, "PQgetf(datetime)");
 998   PQclear(result);
 999 
1000   for (r=0; r < 3; r++)
1001   {
1002     testcnt++;
1003     if (memcmp(&date[r], &dateval[r], sizeof(PGdate)) == 0)
1004       printf("  %%date - #%d passed\n", r+1);
1005     else
1006     {
1007       failcnt++;
1008       fprintf(stderr, "  %%date - #%d FAILED\n", r+1);
1009     }
1010   }
1011 
1012   for (r=0; r < 3; r++)
1013   {
1014     testcnt++;
1015     if (memcmp(&tim[r], &timeval[r], sizeof(PGtime)) == 0)
1016       printf("  %%time - #%d passed\n", r+1);
1017     else
1018     {
1019       failcnt++;
1020       fprintf(stderr, "  %%time - #%d FAILED\n", r+1);
1021     }
1022   }
1023 
1024   for (r=0; r < 3; r++)
1025   {
1026     testcnt++;
1027     if (memcmp(&timetz[r], &timetzval[r], sizeof(PGtime)) == 0)
1028       printf("  %%timetz - #%d passed\n", r+1);
1029     else
1030     {
1031       failcnt++;
1032       fprintf(stderr, "  %%timetz - #%d FAILED\n", r+1);
1033     }
1034   }
1035 
1036   for (r=0; r < 3; r++)
1037   {
1038 
1039     testcnt++;
1040     if (memcmp(&timestamp[r], &timestampval[r], sizeof(PGtimestamp)) == 0)
1041     {
1042       printf("  %%timestamp - #%d passed\n", r+1);
1043     }
1044     /* Windows may hit rounding issues with usecs.  In this test, usecs of
1045      * 7214 were coming back as 7215.
1046      */
1047 #if defined(_WIN32) || defined(_WIN64)
1048     else if (timestamp[r].time.usec+1 == timestampval[r].time.usec ||
1049        timestamp[r].time.usec-1 == timestampval[r].time.usec)
1050     {
1051       printf("  %%timestamp - #%d passed (warning: known usec rounding "
1052         "issue - sent=%d, recv=%d)\n", r+1,
1053         timestampval[r].time.usec, timestamp[r].time.usec);
1054     }
1055 #endif
1056     else
1057     {
1058       failcnt++;
1059       fprintf(stderr, "  %%timestamp - #%d FAILED\n", r+1);
1060     }
1061   }
1062 
1063   /* interval usecs won't match when using a non-ISO datastyle because only
1064    * 2 fractional digits are provided: 23.291876 => 23.29 secs.
1065    */
1066   if (format == 0 && !strstr(datestyle, "ISO"))
1067   {
1068     printf("  **NOTE: non-ISO DateStyle interval text has a 2 digit limit "
1069       "on the microsecond value ... adjusting\n    => sent=%d, received=%d\n",
1070       intervalval.usecs, interval.usecs);
1071     interval.usecs = intervalval.usecs;
1072   }
1073 
1074   testcnt++;
1075   if (memcmp(&interval, &intervalval, sizeof(PGinterval)) == 0)
1076     printf("  %%interval - passed\n");
1077   else
1078   {
1079     failcnt++;
1080     fprintf(stderr, "  %%interval - FAILED\n");
1081 
1082     printf("    SENT: yr=%d,mon=%d,days=%d,hr=%d,min=%d,sec=%d,usec=%d\n",
1083       intervalval.years, intervalval.mons, intervalval.days,
1084       intervalval.hours, intervalval.mins, intervalval.secs, intervalval.usecs);
1085     printf("    RECV: yr=%d,mon=%d,days=%d,hr=%d,min=%d,sec=%d,usec=%d\n",
1086       interval.years, interval.mons, interval.days,
1087       interval.hours, interval.mins, interval.secs, interval.usecs);
1088   }
1089 
1090   testcnt++;
1091 
1092   if (timestamptz.epoch == timestamptzval.epoch)
1093     printf("  %%timestamptz - passed\n");
1094   else
1095   {
1096     failcnt++;
1097     fprintf(stderr, "  %%timestamptz - FAILED\n");
1098 
1099     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",
1100       timestamptzval.epoch,
1101       timestamptzval.date.isbc,timestamptzval.date.year,timestamptzval.date.mon,
1102       timestamptzval.date.mday,timestamptzval.date.jday,timestamptzval.date.yday,
1103       timestamptzval.date.wday,
1104       timestamptzval.time.hour, timestamptzval.time.min, timestamptzval.time.sec,
1105       timestamptzval.time.usec, timestamptzval.time.withtz, timestamptzval.time.isdst,
1106       timestamptzval.time.tzabbr, timestamptzval.time.gmtoff);
1107 
1108     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",
1109       timestamptz.epoch,
1110       timestamptz.date.isbc,timestamptz.date.year,timestamptz.date.mon,
1111       timestamptz.date.mday,timestamptz.date.jday,timestamptz.date.yday,
1112       timestamptz.date.wday,
1113       timestamptz.time.hour, timestamptz.time.min, timestamptz.time.sec,
1114       timestamptz.time.usec, timestamptz.time.withtz, timestamptz.time.isdst,
1115       timestamptz.time.tzabbr, timestamptz.time.gmtoff);
1116   }
1117 }
1118 
1119 static void test_network(int format)
1120 {
1121   int r;
1122   PGinet ipv4, ipv4out;
1123   PGinet ipv6, ipv6out;
1124   PGmacaddr mac = {1,2,3,225,226,227};
1125   PGmacaddr macout;
1126   int have_ipv4=1;
1127   int have_ipv6=1;
1128 
1129 #if defined(_MSC_VER) || defined(__MINGW32__) || defined(HAVE_GETADDRINFO)
1130   struct addrinfo *info = NULL;
1131   struct addrinfo hints = {0};
1132 
1133   memset(&ipv4, 0, sizeof(PGinet));
1134   memset(&ipv4out, 0, sizeof(PGinet));
1135   memset(&ipv6, 0, sizeof(PGinet));
1136   memset(&ipv6out, 0, sizeof(PGinet));
1137 
1138   PQparamReset(param);
1139   printf("\nNetwork types: (%s)\n", format ? "binary" : "text");
1140 
1141   /* this should always work */
1142   if ((r = getaddrinfo("192.168.123.77", NULL, NULL, &info)))
1143   {
1144     have_ipv4 = 0;
1145     //testcnt++;
1146     //failcnt++;
1147     fprintf(stderr, "  IPv4: SKIPPING TEST (no support)\n");
1148   }
1149   else
1150   {
1151     ipv4.mask = 32;
1152     ipv4.sa_buf_len = (int)info->ai_addrlen;
1153     ipv4.is_cidr = 0;
1154     memcpy(ipv4.sa_buf, info->ai_addr, ipv4.sa_buf_len);
1155     memset(ipv4.sa_buf, 0, 2);
1156     ((struct sockaddr *) ipv4.sa_buf)->sa_family = AF_INET;
1157     freeaddrinfo(info);
1158   }
1159 
1160 #ifdef AF_INET6
1161   hints.ai_family = AF_INET6;
1162 #endif
1163 
1164   /* windows may choke on an IPv6 address */
1165   if ((r = getaddrinfo("2001:4f8:3:ba:2e0:81ff:fe22:d1f1", NULL, &hints, &info)))
1166   {
1167     have_ipv6 = 0;
1168     //testcnt++;
1169     //failcnt++;
1170     fprintf(stderr, "  IPv6: SKIPPING TEST (no support - [%d] %s)\n",
1171       r, gai_strerror(r));
1172   }
1173   else
1174   {
1175     ipv6.mask = 32;
1176     ipv6.sa_buf_len = (int)info->ai_addrlen;
1177     ipv6.is_cidr = 0;
1178     memcpy(ipv6.sa_buf, info->ai_addr, ipv6.sa_buf_len);
1179 
1180 #ifdef AF_INET6
1181     memset(ipv6.sa_buf, 0, 2);
1182     ((struct sockaddr *) ipv6.sa_buf)->sa_family = AF_INET6;
1183 #endif
1184 
1185     freeaddrinfo(info);
1186   }
1187 
1188   r = have_ipv4 ? PQputf(param, "%inet", &ipv4) : PQputf(param, "%null");
1189   PUTOKAY(param, r, "PQputf(network-ipv4)");
1190 
1191   r = have_ipv6 ? PQputf(param, "%inet", &ipv6) : PQputf(param, "%null");
1192   PUTOKAY(param, r, "PQputf(network-ipv6)");
1193 
1194   r = PQputf(param, "%macaddr", &mac);
1195   PUTOKAY(param, r, "PQputf(network-macaddr)");
1196 
1197   DROP_TABLE("libpq_network");
1198 
1199   result = PQexec(conn, "CREATE TABLE libpq_network ("
1200     "ipv4 inet, ipv6 inet, mac macaddr)");
1201   CMDOKAY("creating libpq_network table");
1202   PQclear(result);
1203 
1204   result = PQparamExec(conn, param,
1205     "INSERT INTO libpq_network VALUES ($1,$2,$3)", format);
1206   CMDOKAY("PQparamExec(INSERT:network)");
1207   PQclear(result);
1208 
1209   result = PQparamExec(conn, NULL, "SELECT * FROM libpq_network", format);
1210   TUPSOKAY("PQparamExec(SELECT:network)");
1211 
1212   r = PQgetf(result, 0, "%inet %inet %macaddr",
1213     0, &ipv4out, 1, &ipv6out, 2, &macout);
1214   GETOKAY(r, "PQgetf(network)");
1215 
1216   PQclear(result);
1217 
1218   if (have_ipv4)
1219   {
1220     if (memcmp(&ipv4out, &ipv4, sizeof(PGinet)))
1221     {
1222       failcnt++;
1223       fprintf(stderr, "  %%inet - IPv4 FAILED\n");
1224     }
1225     else
1226       printf("  %%inet - IPv4 passed\n");
1227   }
1228 
1229   if (have_ipv6)
1230   {
1231     if (memcmp(&ipv6out, &ipv6, sizeof(PGinet)))
1232     {
1233       failcnt++;
1234       fprintf(stderr, "  %%inet - IPv6 FAILED\n");
1235     }
1236     else
1237       printf("  %%inet - IPv6 passed\n");
1238   }
1239 
1240   testcnt++;
1241   if (memcmp(&macout, &mac, sizeof(PGmacaddr)))
1242   {
1243     failcnt++;
1244     fprintf(stderr, "  %%macaddr - FAILED\n");
1245   }
1246   else
1247     printf("  %%macaddr - passed\n");
1248 #else
1249   printf("\nNetwork types: (%s) - skipping, no getaddrinfo()\n",
1250     format ? "binary" : "text");
1251 #endif
1252 }
1253 
1254 /* PGtypeProc put for epoch sub-class */
1255 static int epoch_put(PGtypeArgs *args)
1256 {
1257   PGtimestamp ts;
1258   time_t t = va_arg(args->ap, time_t);
1259   struct tm *tm = localtime(&t);
1260 
1261   printf("  epoch_put('%ld')\n", t);
1262 
1263   ts.date.isbc = 0;
1264   ts.date.year = tm->tm_year + 1900; /* PGtimestamp requires 4-digit year */
1265   ts.date.mon  = tm->tm_mon;
1266   ts.date.mday = tm->tm_mday;
1267   ts.time.hour = tm->tm_hour;
1268   ts.time.min  = tm->tm_min;
1269   ts.time.sec  = tm->tm_sec;
1270   ts.time.usec = 0;
1271 
1272   PQlocalTZInfo(&t, &ts.time.gmtoff, &ts.time.isdst, NULL);
1273   return args->super(args, &ts);
1274 }
1275 
1276 /* PGtypeProc get for epoch sub-class */
1277 static int epoch_get(PGtypeArgs *args)
1278 {
1279   PGtimestamp ts;
1280   time_t *t = va_arg(args->ap, time_t *);
1281 
1282   printf("  epoch_get(%s)\n", args->format==0 ? "text" : "binary");
1283 
1284   if (args->super(args, &ts) == -1)
1285     return -1; /* args->errorf called by super class */
1286 
1287   /* since PGtimestamp contains an epoch member as an int8, we can
1288    * just copy that value, rather than doing a mktime().  If time_t
1289    * is 32-bits, the below has a limited range compared to an int8.
1290    * Although, this will work fine for timestamps in a 32-bit time_t range.
1291    * 64-bit time_t should always work.
1292    */
1293   *t = (time_t)ts.epoch;
1294   return 0;
1295 }
1296 
1297 static void test_subclass(int format)
1298 {
1299   int r;
1300   PGparam *prm;
1301   time_t timeout[2] = {0};
1302   time_t timevals[] = {0, 946702800}; /* 1970-01-01 00:00:00, 2000-01-01 00:00:00 */
1303 
1304   printf("\nSub-class: (%s)\n", format ? "binary" : "text");
1305 
1306   DROP_TABLE("libpq_subclass");
1307   result = PQexec(conn,
1308     "CREATE TABLE libpq_subclass (n timestamptz, a timestamptz)");
1309   CMDOKAY("creating libpq_subclass table");
1310   PQclear(result);
1311 
1312   prm = PQparamCreate(conn);
1313   if (!PQputf(prm, "%epoch %epoch", timevals[0], timevals[1]))
1314     fprintf(stderr, "subclass_put failed: %s\n", PQgeterror());
1315   result = PQparamExec(conn, prm, "INSERT INTO libpq_subclass VALUES ($1, $2)", format);
1316   PQparamClear(prm);
1317   CMDOKAY("PQparamExec(INSERT:subclass)");
1318   PQclear(result);
1319 
1320   result = PQparamExec(conn, NULL, "SELECT * FROM libpq_subclass", format);
1321   TUPSOKAY("PQparamExec(SELECT:subclass)");
1322 
1323   /* use schema-qualified name */
1324   r = PQgetf(result, 0, "%libpq.epoch %epoch", 0, &timeout[0], 1, &timeout[1]);
1325   GETOKAY(r, "PQgetf(subclass)");
1326   PQclear(result);
1327 
1328   testcnt++;
1329   if (memcmp(timeout, timevals, sizeof(timeout)))
1330   {
1331     failcnt++;
1332     fprintf(stderr, "  %%epoch - FAILED (putval=%ld,%ld, getval=%ld,%ld)\n",
1333       timevals[0], timevals[1], timeout[0], timeout[1]);
1334   }
1335   else
1336     fprintf(stderr, "  %%epoch - passed\n");
1337 }