diff --git a/FU/Pg.pm b/FU/Pg.pm index c058a93..d7d4b68 100644 --- a/FU/Pg.pm +++ b/FU/Pg.pm @@ -638,8 +638,24 @@ stored in a 64-bit integer. If you prefer that, use: =item date -Converted between strings in C format. Postgres accepts a bunch of -alternative date formats, this module does not. +Converted between seconds since Unix epoch as an integer, with the time fixed +at C<00:00:00 UTC>. When used as bind parameter, the time part is truncated. +This format makes for easy comparison with other timestamps, but if you prefer +to work with strings in the C format instead, use: + + $conn->set_type(date => '$date_str'); + +Postgres accepts a bunch of alternative date formats for bind paramaters, this +module does not. + +=item time + +Converted between floating point seconds since C<00:00:00>, supporting +microsecond precision. This format allows for easy comparison against Unix +timestamps (time of day = C<$timestamp % 86400>) and can be added to an integer +date value to form a complete timestamp. + +(There's no support for the string format yet) =item json / jsonb @@ -689,7 +705,7 @@ can work around. =item money -=item time / timetz +=item timetz =item bit / varbit diff --git a/c/pgtypes.c b/c/pgtypes.c index f80e0a1..e6d586a 100644 --- a/c/pgtypes.c +++ b/c/pgtypes.c @@ -553,17 +553,26 @@ SENDFN(uuid) { RECVFN(timestamp) { RLEN(8); - IV ts = fu_frombeI(64, buf); - return newSVnv(((NV)ts / 1000000) + UNIX_PG_EPOCH); + return newSVnv(((NV)fu_frombeI(64, buf) / 1000000) + UNIX_PG_EPOCH); } SENDFN(timestamp) { if (!looks_like_number(val)) SERR("expected a number"); - IV ts = (SvNV(val) - UNIX_PG_EPOCH) * 1000000; + I64 ts = (SvNV(val) - UNIX_PG_EPOCH) * 1000000; fustr_writebeI(64, out, ts); } RECVFN(date) { + RLEN(4); + return newSVuv(((UV)fu_frombeI(32, buf)) * 86400 + UNIX_PG_EPOCH); +} + +SENDFN(date) { + if (!looks_like_number(val)) SERR("expected a number"); + fustr_writebeI(32, out, (SvIV(val) - UNIX_PG_EPOCH) / 86400); +} + +RECVFN(date_str) { RLEN(4); time_t ts = ((time_t)fu_frombeI(32, buf)) * 86400 + UNIX_PG_EPOCH; struct tm tm; @@ -571,7 +580,7 @@ RECVFN(date) { return newSVpvf("%04d-%02d-%02d", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday); } -SENDFN(date) { +SENDFN(date_str) { int year, month, day; if (sscanf(SvPV_nolen(val), "%4d-%2d-%2d", &year, &month, &day) != 3) SERR("invalid date format"); /* Can't use mktime() hackery here because libc has no UTC variant. Code @@ -593,6 +602,16 @@ SENDFN(date) { #undef UNIX_PG_EPOCH +RECVFN(time) { + RLEN(8); + return newSVnv(((NV)fu_frombeI(64, buf)) / 1000000); +} + +SENDFN(time) { + if (!looks_like_number(val)) SERR("expected a number"); + fustr_writebeI(64, out, SvNV(val) * 1000000); +} + #undef SIV #undef RLEN #undef RECVFN @@ -696,7 +715,7 @@ SENDFN(date) { B( 1042, "bpchar", text )\ B( 1043, "varchar", text )\ B( 1082, "date", date )\ - /* 1083 time */\ + B( 1083, "time", time )\ B( 1114, "timestamp", timestamp)\ A( 1115, "_timestamp", 1114 )\ A( 1182, "_date", 1082 )\ @@ -770,9 +789,23 @@ static const fupg_type fupg_builtin[] = { }; #undef BUILTINS - #define FUPG_BUILTIN (sizeof(fupg_builtin) / sizeof(fupg_type)) + +/* List of special types for use with set_type() */ +#define SPECIALS\ + T("$date_str", date_str) + +static const fupg_type fupg_specials[] = { +#define T(name, fun) { 0, 0, {name"\0"}, fupg_send_##fun, fupg_recv_##fun }, + SPECIALS +#undef T +}; + +#undef SPECIALS +#define FUPG_SPECIALS (sizeof(fupg_specials) / sizeof(fupg_type)) + + static const fupg_type fupg_type_perlcb = { 0, 0, {"$perl_cb"}, fupg_send_perlcb, fupg_recv_perlcb }; @@ -793,6 +826,12 @@ static const fupg_type *fupg_builtin_byoid(Oid oid) { static const fupg_type *fupg_builtin_byname(const char *name) { size_t i; + /* XXX: Can use binary search here if the list of specials grows. + * That list does not have to be ordered by oid. */ + for (i=0; i 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 date => 0, undef, '1970-01-01'; +v date => 915753600, undef, '1999-01-08'; +v date => 1740355200, undef, '2025-02-24'; +f date => ''; +f date => '1970-01-01'; + +$conn->set_type(date => '$date_str'); v date => '1970-01-01'; v date => '1999-01-08'; v date => '2025-02-24'; @@ -124,6 +131,13 @@ f date => '2025-'; f date => '2025-02-'; f date => '1999-Jan-08'; +v time => 0, undef, '00:00:00'; +v time => 60.123456, undef, '00:01:00.123456'; +v time => 3600 * 13 + 43 * 60 + 19 + 0.987654, undef, '13:43:19.987654'; +f time => ''; +f time => -1; +f time => 86400.1; + v 'int[]', [], undef, '{}'; v 'int[]', [1], undef, '{1}'; v 'int[]', [1,-3,undef,3], undef, '{1,-3,NULL,3}';