pg: timestamp(tz) types + more docs
This commit is contained in:
parent
8036b8f0bf
commit
6f1583ddad
3 changed files with 121 additions and 5 deletions
99
FU/Pg.pm
99
FU/Pg.pm
|
|
@ -176,11 +176,13 @@ Enable or disable caching of the prepared statement for this particular query.
|
||||||
|
|
||||||
=item $st->text_params($enable)
|
=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</"Formats and Types"> below for what this means.
|
||||||
|
|
||||||
=item $st->text_results($enable)
|
=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</"Formats and Types"> below for what this means.
|
||||||
|
|
||||||
=item $st->text($enable)
|
=item $st->text($enable)
|
||||||
|
|
||||||
|
|
@ -463,7 +465,98 @@ the same time, that won't end well.
|
||||||
|
|
||||||
=head2 Formats and Types
|
=head2 Formats and Types
|
||||||
|
|
||||||
I<TODO>
|
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<text> 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<binary> 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<builtin::true> and C<builtin::false>. As bind
|
||||||
|
parameters, Perl's idea of truthiness is used: C<0>, C<false> and C<""> are
|
||||||
|
false, everything else is true. Objects that overload I<bool> are also
|
||||||
|
supported. C<undef> always converts to SQL C<NULL>.
|
||||||
|
|
||||||
|
=item bytea
|
||||||
|
|
||||||
|
The C<bytea> 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<time()> (or better: C<Time::HiRes::time()>)
|
||||||
|
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<json_parse()> and C<json_format()> from
|
||||||
|
L<FU::Util>.
|
||||||
|
|
||||||
|
While C<null> is a valid JSON value, there's currently no way to distinguish
|
||||||
|
that from SQL C<NULL>. When sending C<undef> as bind parameter, it is sent as
|
||||||
|
SQL C<NULL>.
|
||||||
|
|
||||||
|
=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<TODO:> Methods to convert between the various formats.
|
||||||
|
|
||||||
|
I<TODO:> Methods to query type info.
|
||||||
|
|
||||||
|
I<TODO:> Custom per-type configuration.
|
||||||
|
|
||||||
=head2 Errors
|
=head2 Errors
|
||||||
|
|
||||||
|
|
|
||||||
21
c/pgtypes.c
21
c/pgtypes.c
|
|
@ -486,6 +486,23 @@ SENDFN(uuid) {
|
||||||
if (dig != 0x10 || bytes != 16) SERR("invalid 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 SIV
|
||||||
#undef RLEN
|
#undef RLEN
|
||||||
#undef RECVFN
|
#undef RECVFN
|
||||||
|
|
@ -590,11 +607,11 @@ SENDFN(uuid) {
|
||||||
B( 1043, "varchar", text )\
|
B( 1043, "varchar", text )\
|
||||||
/* 1082 date */\
|
/* 1082 date */\
|
||||||
/* 1083 time */\
|
/* 1083 time */\
|
||||||
|
B( 1114, "timestamp", timestamp)\
|
||||||
A( 1115, "_timestamp", 1114 )\
|
A( 1115, "_timestamp", 1114 )\
|
||||||
/* 1114 timestamp */\
|
|
||||||
A( 1182, "_date", 1082 )\
|
A( 1182, "_date", 1082 )\
|
||||||
A( 1183, "_time", 1083 )\
|
A( 1183, "_time", 1083 )\
|
||||||
/* 1184 timestamptz */\
|
B( 1184, "timestamptz", timestamp)\
|
||||||
A( 1185, "_timestamptz", 1184 )\
|
A( 1185, "_timestamptz", 1184 )\
|
||||||
/* 1186 interval */\
|
/* 1186 interval */\
|
||||||
A( 1187, "_interval", 1186 )\
|
A( 1187, "_interval", 1186 )\
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,12 @@ f uuid => 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a111';
|
||||||
f uuid => 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1';
|
f uuid => 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1';
|
||||||
f uuid => 'a0egbc99-9c0b-4ef8-bb6d-6bb9bd380a11';
|
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[]', [], undef, '{}';
|
||||||
v 'int[]', [1], undef, '{1}';
|
v 'int[]', [1], undef, '{1}';
|
||||||
v 'int[]', [1,-3,undef,3], undef, '{1,-3,NULL,3}';
|
v 'int[]', [1,-3,undef,3], undef, '{1,-3,NULL,3}';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue