On Wed, Mar 30, 2022 at 01:16:37PM -0400, Greg Stark wrote:
> On Mon, 28 Feb 2022 at 17:50, Tom Lane <t...@sss.pgh.pa.us> wrote:
> >
> > Chapman Flack <c...@anastigmatix.net> writes:
> > > In the current state of affairs, what's considered the ur-source of that
> > > information?
> >
> > The source code for the type's send/receive functions :-(.  One could
> > wish for something better, but no one has stepped up to produce such
> > documentation.
> 
> Fwiw the client library I heard of attempting to have good binary mode
> support was the Crystal language client
> https://github.com/will/crystal-pg. I think he was aiming for full
> coverage of the built-in data types. That might make a good reference
> implementation to write up documentation from. He probably uncovered
> some corner cases in development that one might not find from just
> inspection of the server code.

Checking in for quick feedback to see if this refactor makes sense.

I've created a function for each data type with the idea that an example
for handling a specific data type can be more easily reviewed by looking
in a single place.

I've added examples for REAL, TIMESTAMP WITHOUT TIME ZONE, and BOOLEAN
to try to illustrate how testlibpq3.sql and testlibpq3.c will grow if
this is a good way to go.

Regards,
Mark
diff --git a/src/test/examples/testlibpq3.c b/src/test/examples/testlibpq3.c
index 4f7b791388..68972f17f3 100644
--- a/src/test/examples/testlibpq3.c
+++ b/src/test/examples/testlibpq3.c
@@ -11,21 +11,56 @@
  * CREATE SCHEMA testlibpq3;
  * SET search_path = testlibpq3;
  * SET standard_conforming_strings = ON;
- * CREATE TABLE test1 (i int4, t text, b bytea);
- * INSERT INTO test1 values (1, 'joe''s place', '\000\001\002\003\004');
- * INSERT INTO test1 values (2, 'ho there', '\004\003\002\001\000');
+ * CREATE SCHEMA testlibpq3;
+ * SET search_path = testlibpq3;
+ * SET standard_conforming_strings = ON;
+ * CREATE TABLE test1 (
+ *     i int4
+ *   , r real
+ *   , bo boolean
+ *   , ts timestamp
+ *   , t text
+ *   , b bytea
+ * );
+ * INSERT INTO test1
+ * VALUES (
+ *     1
+ *   , 3.141593
+ *   , true
+ *   , '2000-01-01 00:00:02.414213'
+ *   , 'joe''s place'
+ *   , '\000\001\002\003\004'
+ * );
+ * INSERT INTO test1
+ * VALUES (
+ *     2
+ *   , 1.618033
+ *   , false
+ *   , '2000-01-01 00:00:01.465571'
+ *   , 'ho there'
+ *   , '\004\003\002\001\000'
+ * );
  *
  * The expected output is:
  *
  * tuple 0: got
  *     i = (4 bytes) 1
+ *     r = (4 bytes) 3.141593
+ *     bo = (1 bytes) 1
  *     t = (11 bytes) 'joe's place'
  *     b = (5 bytes) \000\001\002\003\004
  *
  * tuple 0: got
  *     i = (4 bytes) 2
+ *     r = (4 bytes) 1.618033
+ *     bo = (1 bytes) 0
  *     t = (8 bytes) 'ho there'
  *     b = (5 bytes) \004\003\002\001\000
+ *
+ * General notes about this example:
+ *
+ * Use PQfnumber to avoid assumptions about field order in result but when
+ * getting the field values we ignore possibility they are null!
  */
 
 #ifdef WIN32
@@ -36,6 +71,7 @@
 #include <stdlib.h>
 #include <stdint.h>
 #include <string.h>
+#include <time.h>
 #include <sys/types.h>
 #include "libpq-fe.h"
 
@@ -44,6 +80,13 @@
 #include <arpa/inet.h>
 
 
+/* These macros hopefully make reading calculations for timestamps easier. */
+#define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */
+#define UNIX_EPOCH_JDATE 2440588 /* == date2j(1970, 1, 1) */
+#define SECS_PER_DAY 86400
+
+uint64_t ntohll(uint64_t);
+
 static void
 exit_nicely(PGconn *conn)
 {
@@ -51,6 +94,142 @@ exit_nicely(PGconn *conn)
        exit(1);
 }
 
+static void
+handle_boolean(PGresult *res, int i)
+{
+       int                     fnum;
+       char       *ptr;
+       int                     val;
+
+       fnum = PQfnumber(res, "bo");
+       ptr = PQgetvalue(res, i, fnum);
+       val = (int) *ptr;
+       printf(" bo = (%d bytes) %d\n", PQgetlength(res, i, fnum), val);
+}
+
+static void
+handle_bytea(PGresult *res, int i)
+{
+       int                     j;
+       int                     fnum;
+       char       *ptr;
+       int                     len;
+
+       fnum = PQfnumber(res, "b");
+       ptr = PQgetvalue(res, i, fnum);
+
+       /*
+        * The binary representation of BYTEA is a bunch of bytes, which could
+        * include embedded nulls so we have to pay attention to field length.
+        */
+       len = PQgetlength(res, i, fnum);
+       printf(" b = (%d bytes) ", len);
+       for (j = 0; j < len; j++) printf("\\%03o", ptr[j]);
+}
+
+static void
+handle_integer(PGresult *res, int i)
+{
+       int                     fnum;
+       char       *ptr;
+       int                     val;
+
+       fnum = PQfnumber(res, "i");
+       ptr = PQgetvalue(res, i, fnum);
+
+       /*
+        * The binary representation of INT4 is in network byte order, which
+        * we'd better coerce to the local byte order.
+        */
+       val = ntohl(*((uint32_t *) ptr));
+
+       printf(" i = (%d bytes) %d\n", PQgetlength(res, i, fnum), val);
+}
+
+static void
+handle_real(PGresult *res, int i)
+{
+       int                     fnum;
+       char       *ptr;
+       union {
+               int             i;
+               float   f;
+       }                       val;
+
+       fnum = PQfnumber(res, "r");
+       ptr = PQgetvalue(res, i, fnum);
+
+       /*
+        * The binary representation of INT4 is in network byte order, which
+        * we'd better coerce to the local byte order.
+        */
+       val.i = ntohl(*((uint32_t *) ptr));
+
+       printf(" r = (%d bytes) %f\n", PQgetlength(res, i, fnum), val.f);
+}
+
+static void
+handle_text(PGresult *res, int i)
+{
+       int                     fnum;
+       char       *ptr;
+
+       fnum = PQfnumber(res, "t");
+       ptr = PQgetvalue(res, i, fnum);
+
+       /*
+        * The binary representation of TEXT is, well, text, and since libpq was
+        * nice enough to append a zero byte to it, it'll work just fine as a C
+        * string.
+        */
+       printf(" t = (%d bytes) '%s'\n", PQgetlength(res, i, fnum), ptr);
+}
+
+static void
+handle_timestamp(PGresult *res, int i)
+{
+       int                     fnum;
+       char       *ptr;
+       uint64_t        val;
+
+       struct tm  *tm;
+       time_t          timep;
+       uint32_t        mantissa;
+
+       fnum = PQfnumber(res, "ts");
+       ptr = PQgetvalue(res, i, fnum);
+       val = ntohll(*((uint64_t *) ptr));
+
+       /*
+        * The binary representation of a timestamp is in microseconds
+        * from 2000-01-01.
+        */
+       timep = val / (uint64_t) 1000000 +
+                       (uint64_t) (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) *
+                       (uint64_t) SECS_PER_DAY;
+       mantissa = val - (uint64_t) (timep -
+                       (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * 
SECS_PER_DAY) *
+                       (uint64_t) 1000000;
+
+       /* For ease of testing, assume and print timestamps in GMT. */
+       tm = gmtime(&timep);
+
+       printf(" ts = (%d bytes) %04d-%02d-%02d %02d:%02d:%02d.%06d\n",
+                       PQgetlength(res, i, fnum), tm->tm_year + 1900, 
tm->tm_mon + 1,
+                       tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, 
mantissa);
+}
+
+/* This is a uint64_t version of ntohl from arpa/inet.h. */
+uint64_t
+ntohll(uint64_t netlonglong)
+{
+       if (ntohl(1) == 1)
+               return netlonglong;
+       else
+               return (uint64_t) (ntohl((int) ((netlonglong << 32) >> 32))) << 
32 |
+                               (uint64_t) ntohl(((int) (netlonglong >> 32)));
+}
+
 /*
  * This function prints a query result that is a binary-format fetch from
  * a table defined as in the comment above.  We split it out because the
@@ -59,54 +238,17 @@ exit_nicely(PGconn *conn)
 static void
 show_binary_results(PGresult *res)
 {
-       int                     i,
-                               j;
-       int                     i_fnum,
-                               t_fnum,
-                               b_fnum;
-
-       /* Use PQfnumber to avoid assumptions about field order in result */
-       i_fnum = PQfnumber(res, "i");
-       t_fnum = PQfnumber(res, "t");
-       b_fnum = PQfnumber(res, "b");
+       int                     i;
 
        for (i = 0; i < PQntuples(res); i++)
        {
-               char       *iptr;
-               char       *tptr;
-               char       *bptr;
-               int                     blen;
-               int                     ival;
-
-               /* Get the field values (we ignore possibility they are null!) 
*/
-               iptr = PQgetvalue(res, i, i_fnum);
-               tptr = PQgetvalue(res, i, t_fnum);
-               bptr = PQgetvalue(res, i, b_fnum);
-
-               /*
-                * The binary representation of INT4 is in network byte order, 
which
-                * we'd better coerce to the local byte order.
-                */
-               ival = ntohl(*((uint32_t *) iptr));
-
-               /*
-                * The binary representation of TEXT is, well, text, and since 
libpq
-                * was nice enough to append a zero byte to it, it'll work just 
fine
-                * as a C string.
-                *
-                * The binary representation of BYTEA is a bunch of bytes, 
which could
-                * include embedded nulls so we have to pay attention to field 
length.
-                */
-               blen = PQgetlength(res, i, b_fnum);
-
                printf("tuple %d: got\n", i);
-               printf(" i = (%d bytes) %d\n",
-                          PQgetlength(res, i, i_fnum), ival);
-               printf(" t = (%d bytes) '%s'\n",
-                          PQgetlength(res, i, t_fnum), tptr);
-               printf(" b = (%d bytes) ", blen);
-               for (j = 0; j < blen; j++)
-                       printf("\\%03o", bptr[j]);
+               handle_integer(res, i);
+               handle_real(res, i);
+               handle_boolean(res, i);
+               handle_timestamp(res, i);
+               handle_text(res, i);
+               handle_bytea(res, i);
                printf("\n\n");
        }
 }
diff --git a/src/test/examples/testlibpq3.sql b/src/test/examples/testlibpq3.sql
index 35a95ca347..94cb2b97b0 100644
--- a/src/test/examples/testlibpq3.sql
+++ b/src/test/examples/testlibpq3.sql
@@ -1,6 +1,29 @@
 CREATE SCHEMA testlibpq3;
 SET search_path = testlibpq3;
 SET standard_conforming_strings = ON;
-CREATE TABLE test1 (i int4, t text, b bytea);
-INSERT INTO test1 values (1, 'joe''s place', '\000\001\002\003\004');
-INSERT INTO test1 values (2, 'ho there', '\004\003\002\001\000');
+CREATE TABLE test1 (
+    i int4
+  , r real
+  , bo boolean
+  , ts timestamp
+  , t text
+  , b bytea
+);
+INSERT INTO test1
+VALUES (
+    1
+  , 3.141593
+  , true
+  , '2000-01-01 00:00:02.414213'
+  , 'joe''s place'
+  , '\000\001\002\003\004'
+);
+INSERT INTO test1
+VALUES (
+    2
+  , 1.618033
+  , false
+  , '2000-01-01 00:00:01.465571'
+  , 'ho there'
+  , '\004\003\002\001\000'
+);

Reply via email to