pg: Module rename + more docs
This commit is contained in:
parent
ccc2f1dbf0
commit
33fe0d98a8
7 changed files with 186 additions and 101 deletions
426
FU/PG.pm
426
FU/PG.pm
|
|
@ -1,426 +0,0 @@
|
|||
package FU::PG 0.1;
|
||||
use v5.36;
|
||||
use FU::XS;
|
||||
|
||||
_load_libpq();
|
||||
|
||||
package FU::PG::conn {
|
||||
sub lib_version { FU::PG::lib_version() }
|
||||
};
|
||||
|
||||
package FU::PG::error {
|
||||
use overload '""' => sub($e, @) { $e->{full_message} };
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
FU::PG - Another PostgreSQL client module
|
||||
|
||||
=head1 SYNOPSYS
|
||||
|
||||
my $conn = FU::PG->connect("dbname=test user=test password=nottest");
|
||||
|
||||
$conn->exec('CREATE TABLE books (id SERIAL, title text)');
|
||||
|
||||
$conn->q('INSERT INTO books (title) VALUES ($1)', 'Revelation Space')->exec;
|
||||
|
||||
for my ($id, $title) ($conn->q('SELECT * FROM books')->flat->@*) {
|
||||
print "$id: $title\n";
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
FU::PG is a PostgreSQL client module that (attempts) to set itself apart from
|
||||
the existing alternatives by offering the following features:
|
||||
|
||||
=over
|
||||
|
||||
=item * Automatic conversion of complex types (like JSON, hstore, records, etc)
|
||||
to and from convenient corresponding perl values.
|
||||
|
||||
=item * Support for custom types.
|
||||
|
||||
=item * Configurable Perl representation of timestamp values (or, well, really
|
||||
for any type).
|
||||
|
||||
=item * Data is transfered in the binary format (which may or may not be more
|
||||
efficient, need benchmarks).
|
||||
|
||||
=item * Convenient and high-level API.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Connection setup
|
||||
|
||||
=over
|
||||
|
||||
=item B<< FU::PG->connect($string) >>
|
||||
|
||||
Connect to the PostgreSQL server and return a new C<FU::PG::conn> object.
|
||||
C<$string> can either be in key=value format or a URI, refer to L<the
|
||||
PostgreSQL
|
||||
documentation|https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>
|
||||
for the full list of supported formats and options. You may also pass an empty
|
||||
string and leave the configuration up L<environment
|
||||
variables|https://www.postgresql.org/docs/current/libpq-envars.html>.
|
||||
|
||||
=item B<< $conn->server_version >>
|
||||
|
||||
Returns the version of the PostgreSQL server as an integer in the format of
|
||||
C<$major * 10000 + $minor>. For example, returns 170002 for PostgreSQL 17.2.
|
||||
|
||||
=item B<< $conn->lib_version >>
|
||||
|
||||
Returns the libpq version in the same format as the C<server_version> method.
|
||||
Also available directly as C<FU::PG::lib_version()>.
|
||||
|
||||
=item B<< $conn->status >>
|
||||
|
||||
Returns a string indicating the status of the connection. Note that this method
|
||||
does not verify that the connection is still alive, the status is updated after
|
||||
each command. Possible return values:
|
||||
|
||||
=over
|
||||
|
||||
=item idle
|
||||
|
||||
Awaiting commands, not in a transaction.
|
||||
|
||||
=item txn_idle
|
||||
|
||||
Awaiting commands, inside a transaction.
|
||||
|
||||
=item txn_done
|
||||
|
||||
Idle, but a transaction object still exists. The connection is unusable until
|
||||
that object goes out of scope.
|
||||
|
||||
=item txn_error
|
||||
|
||||
Inside a transaction that is in an error state. The transaction must be rolled
|
||||
back in order to recover to a usable state. This happens automatically when the
|
||||
transaction object goes out of scope.
|
||||
|
||||
=item bad
|
||||
|
||||
Connection is dead or otherwise unusable.
|
||||
|
||||
=back
|
||||
|
||||
=item B<< $conn->cache($enable) >>
|
||||
|
||||
=item B<< $conn->text_params($enable) >>
|
||||
|
||||
=item B<< $conn->text_results($enable) >>
|
||||
|
||||
=item B<< $conn->text($enable) >>
|
||||
|
||||
Set the default settings for new statements created with B<< $conn->q() >>.
|
||||
|
||||
=item B<< $conn->disconnect >>
|
||||
|
||||
Close the connection. Any active transactions are rolled back and any further
|
||||
attempts to use C<$conn> throw an error.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Querying
|
||||
|
||||
=over
|
||||
|
||||
=item B<< $conn->exec($sql) >>
|
||||
|
||||
Execute one or more SQL commands, separated by a semicolon. Returns the number
|
||||
of rows affected by the last statement or I<undef> if that information is not
|
||||
available for the given command (like `CREATE TABLE`).
|
||||
|
||||
=item B<< $conn->q($sql, @params) >>
|
||||
|
||||
Create a new SQL statement with the given C<$sql> string and an optional list
|
||||
of bind parameters. C<$sql> can only hold a single statement.
|
||||
|
||||
Parameters can be referenced from C<$sql> with numbered placeholders, where
|
||||
C<$1> refers to the first parameter, C<$2> to the second, etc. Be careful to
|
||||
not accidentally interpolate perl's C<$1> and C<$2>. Using a question mark for
|
||||
placeholders, as is common with L<DBI>, is not supported. An error is thrown
|
||||
when attempting to execute a query where the number of C<@params> does not
|
||||
match the number of placeholders in C<$sql>.
|
||||
|
||||
Note that this method just creates a statement object, the given query is not
|
||||
prepared or executed until the appropriate statement methods (see below) are
|
||||
used.
|
||||
|
||||
=back
|
||||
|
||||
Statement objects returned by C<< $conn->q() >> support the following
|
||||
configuration parameters:
|
||||
|
||||
=over
|
||||
|
||||
=item B<< $st->cache($enable) >>
|
||||
|
||||
Enable or disable caching of the prepared statement for this particular query.
|
||||
|
||||
=item B<< $st->text_params($enable) >>
|
||||
|
||||
Enable or disable sending bind parameters in the text format.
|
||||
|
||||
=item B<< $st->text_results($enable) >>
|
||||
|
||||
Enable or disable receiving query results in the text format.
|
||||
|
||||
=item B<< $st->text($enable) >>
|
||||
|
||||
Shorthand for setting C<text_params> and C<text_results> at the same time.
|
||||
|
||||
=back
|
||||
|
||||
Statement objects can be inspected with the following two methods:
|
||||
|
||||
=over
|
||||
|
||||
=item B<< $st->params >>
|
||||
|
||||
Returns an arrayref of hashrefs describing each parameter in the given C<$sql>
|
||||
string. Each parameter only has a single key for now: C<oid>, indicating the
|
||||
type Oid. Example:
|
||||
|
||||
my $params = $conn->q('SELECT id FROM books WHERE id = $1')->params;
|
||||
# $params = [ { oid => 23 } ]
|
||||
|
||||
my $params = $conn->q('SELECT id FROM books')->params;
|
||||
# $params = []
|
||||
|
||||
I<TODO: Resolve the oid to a more human-readable type>
|
||||
|
||||
=item B<< $st->columns >>
|
||||
|
||||
Returns an arrayref of hashrefs describing each column that the statement
|
||||
returns.
|
||||
|
||||
my $cols = $conn->q('SELECT id, title FROM books')->columns;
|
||||
# $cols = [
|
||||
# { name => 'id', oid => 23 },
|
||||
# { name => 'title', oid => 25 },
|
||||
# ]
|
||||
|
||||
|
||||
=back
|
||||
|
||||
The statement can be executed with one of the following methods, depending on
|
||||
how you'd like to obtain the results:
|
||||
|
||||
=over
|
||||
|
||||
=item B<< $st->exec >>
|
||||
|
||||
Execute the query and return the number of rows affected. Similar to C<<
|
||||
$conn->exec >>.
|
||||
|
||||
=item B<< $st->val >>
|
||||
|
||||
Return the first column of the first row. Throws an error if the query does not
|
||||
return exactly one column, or if multiple rows are returned. Returns I<undef>
|
||||
if no rows are returned or if its value is I<NULL>.
|
||||
|
||||
=item B<< $st->rowl >>
|
||||
|
||||
Return the first row as a list. Throws an error if the query does not return
|
||||
exactly one row.
|
||||
|
||||
=item B<< $st->rowa >>
|
||||
|
||||
Return the first row as an arrayref, equivalent to C<< [$st->rowl] >> but
|
||||
probably slightly more efficient.
|
||||
|
||||
=item B<< $st->rowh >>
|
||||
|
||||
Return the first row as a hashref. Also throws an error if the query returns
|
||||
multiple columns with the same name.
|
||||
|
||||
=back
|
||||
|
||||
The only time you actually need to assign a statement object to a variable is
|
||||
when you want to inspect C<params> or C<columns>, in all other cases you can
|
||||
chain the methods for more concise code. For example:
|
||||
|
||||
my @cols = $conn->q('SELECT a, b FROM table')->cache(0)->text->rowa;
|
||||
|
||||
|
||||
=head2 Transactions
|
||||
|
||||
This module provides a convenient and safe API for I<scoped transactions> and
|
||||
I<subtransactions>. A new transaction can be started with C<< $conn->txn >>,
|
||||
which returns an object that can be used to run commands inside the transaction
|
||||
and control its fate. When the object goes out of scope, the transaction is
|
||||
automatically rolled back if no explicit C<< $txn->commit >> has been
|
||||
performed. Any attempts to run queries on the parent C<< $conn >> object will
|
||||
fail while a transaction object is alive.
|
||||
|
||||
{
|
||||
# start a new transaction
|
||||
my $txn = $conn->txn;
|
||||
|
||||
# run queries
|
||||
$txn->q('DELETE FROM books WHERE id = $1', 1)->exec;
|
||||
|
||||
# run commands in a subtransaction
|
||||
{
|
||||
my $subtxn = $txn->txn;
|
||||
# ...
|
||||
}
|
||||
|
||||
# commit
|
||||
$txn->commit;
|
||||
|
||||
# If $txn->commit has not been called, the transaction will be rolled back
|
||||
# automatically when it goes out of scope.
|
||||
}
|
||||
|
||||
Transaction methods:
|
||||
|
||||
=over
|
||||
|
||||
=item B<< $txn->exec(..) >>
|
||||
|
||||
=item B<< $txn->q(..) >>
|
||||
|
||||
Run a query inside the transaction. These work the same as the respective
|
||||
methods on the parent C<$conn> object.
|
||||
|
||||
=item B<< $txn->commit >>
|
||||
|
||||
=item B<< $txn->rollback >>
|
||||
|
||||
Commit or abort the transaction. Any attempts to run queries on this
|
||||
transaction object after this call will throw an error.
|
||||
|
||||
Calling C<rollback> is optional, the transaction is automatically rolled back
|
||||
when the object goes out of scope.
|
||||
|
||||
=item B<< $txn->cache($enable) >>
|
||||
|
||||
=item B<< $txn->text_params($enable) >>
|
||||
|
||||
=item B<< $txn->text_results($enable) >>
|
||||
|
||||
=item B<< $txn->text($enable) >>
|
||||
|
||||
Set the default settings for new statements created with B<< $txn->q() >>.
|
||||
|
||||
These settings are inherited from the main connection when the transaction is
|
||||
created. Subtransactions inherit these settings from their parent transaction.
|
||||
Changing these settings within a transaction does not affect the main
|
||||
connection or any already existing subtransactions.
|
||||
|
||||
=item B<< $txn->txn >>
|
||||
|
||||
Create a subtransaction within the current transaction. A subtransaction works
|
||||
exactly the same as a top-level transaction.
|
||||
|
||||
=item B<< $txn->status >>
|
||||
|
||||
Like C<< $conn->status >>, but with the following status codes:
|
||||
|
||||
=over
|
||||
|
||||
=item idle
|
||||
|
||||
Current transaction is active and awaiting commands.
|
||||
|
||||
=item done
|
||||
|
||||
Current transaction has either been committed or rolled back, further commands
|
||||
will throw an error.
|
||||
|
||||
=item error
|
||||
|
||||
Current transaction is in error state and must be rolled back.
|
||||
|
||||
=item txn_idle
|
||||
|
||||
A subtransaction is active and awaiting commands. The current transaction is
|
||||
not usable until the subtransaction goes out of scope.
|
||||
|
||||
(This status code is also returned when the subtransaction is 'done', the
|
||||
current implementation does not track subtransactions that closely)
|
||||
|
||||
=item txn_error
|
||||
|
||||
A subtransaction is in error state and awaiting to be rolled back.
|
||||
|
||||
=item bad
|
||||
|
||||
Connection is dead or otherwise unusable.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
Of course, if you prefer the old-fashioned manual approach to transaction
|
||||
handling, that is still available:
|
||||
|
||||
$conn->exec('BEGIN');
|
||||
# We're now inside a transaction
|
||||
$conn->exec('COMMIT') or $conn->exec('ROLLBACK');
|
||||
|
||||
Just don't try to use transaction objects and manual transaction commands at
|
||||
the same time, that won't end well.
|
||||
|
||||
|
||||
=head2 Errors
|
||||
|
||||
I<TODO>
|
||||
|
||||
=head1 LIMITATIONS
|
||||
|
||||
=over
|
||||
|
||||
=item * Does not support older versions of libpq or PostgreSQL. Currently only
|
||||
tested with version 17, but versions a bit older than that ought to work fine
|
||||
as well. Much older versions will certainly not work fine.
|
||||
|
||||
=item * (Probably) not thread-safe.
|
||||
|
||||
=item * Only supports the UTF-8 encoding for all text strings sent to and
|
||||
received from the PostgreSQL server. The encoding is assumed to be UTF-8 by
|
||||
default, but if this may not be the case in your situation, setting
|
||||
`client_encoding=utf8` as part of the connection string or manually switching
|
||||
to it after C<connect()> is always safe:
|
||||
|
||||
my $conn = FU::PG->connect('');
|
||||
$conn->exec('SET client_encoding=utf8');
|
||||
|
||||
=item * Only works with blocking (synchronous) calls, not very suitable for use
|
||||
in asynchronous frameworks unless you know your queries are fast and you have a
|
||||
low-latency connection with the Postgres server.
|
||||
|
||||
=back
|
||||
|
||||
Missing features (for now): I<pretty much everything>.
|
||||
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
=over
|
||||
|
||||
=item L<DBD::Pg>
|
||||
|
||||
The venerable Postgres driver for DBI. More stable, portable and battle-tested
|
||||
than this module, but type conversions may leave things to be desired.
|
||||
|
||||
=item L<Pg::PQ>
|
||||
|
||||
A thin wrapper around libpq. Lacks many higher-level conveniences and does not
|
||||
support binary transfers (at the time of writing, but then again there's little
|
||||
benefit in dealing with the binary format in pure perl anyway).
|
||||
|
||||
=item L<DBIx::Simple>
|
||||
|
||||
A popular DBI wrapper with some API conveniences. I may have taken some
|
||||
inspiration from it in the design of this module's API.
|
||||
|
||||
=back
|
||||
Loading…
Add table
Add a link
Reference in a new issue