From 6f1583ddad58bef32496f500159223bb336ac269 Mon Sep 17 00:00:00 2001 From: Yorhel Date: Fri, 21 Feb 2025 12:58:24 +0100 Subject: [PATCH] pg: timestamp(tz) types + more docs --- FU/Pg.pm | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++-- c/pgtypes.c | 21 ++++++++++-- t/pgtypes.t | 6 ++++ 3 files changed, 121 insertions(+), 5 deletions(-) diff --git a/FU/Pg.pm b/FU/Pg.pm index 108d02b..5128ba1 100644 --- a/FU/Pg.pm +++ b/FU/Pg.pm @@ -176,11 +176,13 @@ Enable or disable caching of the prepared statement for this particular query. =item $st->text_params($enable) -Enable or disable sending bind parameters in the text format. +Enable or disable sending bind parameters in the text format. See +L below for what this means. =item $st->text_results($enable) -Enable or disable receiving query results in the text format. +Enable or disable receiving query results in the text format. See +L below for what this means. =item $st->text($enable) @@ -463,7 +465,98 @@ the same time, that won't end well. =head2 Formats and Types -I +The PostgreSQL wire protocol supports sending bind parameters and receiving +query results in two different formats: text and binary. While the exact wire +protocol is an implementation detail that you don't have to worry about, this +module does have a different approach to processing the two formats. + +When you enable C mode, your bind parameters are sent verbatim, as text, +to the PostgreSQL server, where they are then parsed, validated and +interpreted. Likewise, when receiving query results in text mode, it is the +PostreSQL server that is formatting the data into textual strings. Text mode is +essentially a way to tell this module: "don't try to interpret my data, just +send and receive everything as text!" + +Instead, in the (default) C mode, the responsibility of converting +Postgres data to and from Perl values lies with this module. This allows for a +lot of type-specific conveniences, but has the downside of requiring special +code for each supported PostgreSQL type. Most of the Postgres core types are +supported by this module and convert in an intuitive way, but here's a few +type-specific notes: + +=over + +=item bool + +Boolean values are converted to C and C. As bind +parameters, Perl's idea of truthiness is used: C<0>, C and C<""> are +false, everything else is true. Objects that overload I are also +supported. C always converts to SQL C. + +=item bytea + +The C type represents arbitrary binary data and this module will pass +that along as raw binary strings. + +=item timestamp / timestamptz + +These are converted to and from seconds since the Unix epoch as a floating +point value, similar to the C (or better: C) +functions. + +The timestamp types in Postgres have microsecond accuracy. Floating point can +represent that without loss for dates that are near enough to the epoch (still +seems to be fine in 2025, at least), but this conversion may be lossy for dates +far beyond or before the epoch. + +=item json / jsonb + +These types are converted through C and C from +L. + +While C is a valid JSON value, there's currently no way to distinguish +that from SQL C. When sending C as bind parameter, it is sent as +SQL C. + +=item arrays + +PostgreSQL arrays automatically convert to and from Perl arrays as you'd +expect. Arrays in PostgreSQL have the rather unusual feature that the starting +index can be changed for each individual array, but this module doesn't support +that. All arrays received from Postgres will use Perl's usual 0-based indexing +and all arrays sent to Postgres will use their default 1-based indexing. + +=item records / row types + +These are converted to and from hashrefs. + +=item geometric types + +=item numeric + +=item macaddr + +=item money + +=item date / time / timetz + +=item bit / varbit + +=item tsvector / tsquery + +=item Extension types + +These are not supported at the moment. Not that they're hard to implement (I +think), I simply haven't looked into them yet. Open a bug report if you need +any of these. + +=back + +I Methods to convert between the various formats. + +I Methods to query type info. + +I Custom per-type configuration. =head2 Errors diff --git a/c/pgtypes.c b/c/pgtypes.c index 677d8b5..e359326 100644 --- a/c/pgtypes.c +++ b/c/pgtypes.c @@ -486,6 +486,23 @@ SENDFN(uuid) { if (dig != 0x10 || bytes != 16) SERR("invalid UUID"); } +/* Postgres uses 2000-01-01 as epoch, we stick with POSIX 1970-01-01 */ +#define UNIX_PG_EPOCH (10957*86400) + +RECVFN(timestamp) { + RLEN(8); + IV ts = fu_frombeI(64, buf); + return newSVnv(((double)ts / 1000000) + UNIX_PG_EPOCH); +} + +SENDFN(timestamp) { + if (!looks_like_number(val)) SERR("expected a number"); + IV ts = (SvNV(val) - UNIX_PG_EPOCH) * 1000000; + fustr_writebeI(64, out, ts); +} + +#undef UNIX_PG_EPOCH + #undef SIV #undef RLEN #undef RECVFN @@ -590,11 +607,11 @@ SENDFN(uuid) { B( 1043, "varchar", text )\ /* 1082 date */\ /* 1083 time */\ + B( 1114, "timestamp", timestamp)\ A( 1115, "_timestamp", 1114 )\ - /* 1114 timestamp */\ A( 1182, "_date", 1082 )\ A( 1183, "_time", 1083 )\ - /* 1184 timestamptz */\ + B( 1184, "timestamptz", timestamp)\ A( 1185, "_timestamptz", 1184 )\ /* 1186 interval */\ A( 1187, "_interval", 1186 )\ diff --git a/t/pgtypes.t b/t/pgtypes.t index 175a266..ac76c36 100644 --- a/t/pgtypes.t +++ b/t/pgtypes.t @@ -107,6 +107,12 @@ f uuid => 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a111'; f uuid => 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1'; f uuid => 'a0egbc99-9c0b-4ef8-bb6d-6bb9bd380a11'; +$conn->exec('SET timezone=utc'); +v timestamptz => 0, undef, '1970-01-01 00:00:00+00'; +v timestamptz => 1740133814.705915, undef, '2025-02-21 10:30:14.705915+00'; +v timestamp => 0, undef, '1970-01-01 00:00:00'; +v timestamp => 1740133814.705915, undef, '2025-02-21 10:30:14.705915'; + v 'int[]', [], undef, '{}'; v 'int[]', [1], undef, '{1}'; v 'int[]', [1,-3,undef,3], undef, '{1,-3,NULL,3}';