pg: Module rename + more docs
This commit is contained in:
parent
ccc2f1dbf0
commit
33fe0d98a8
7 changed files with 186 additions and 101 deletions
36
FU.xs
36
FU.xs
|
|
@ -49,15 +49,15 @@ fupg_st * FUPG_ST
|
||||||
|
|
||||||
INPUT
|
INPUT
|
||||||
FUPG_CONN
|
FUPG_CONN
|
||||||
if (sv_derived_from($arg, \"FU::PG::conn\")) $var = (fupg_conn *)SvIVX(SvRV($arg));
|
if (sv_derived_from($arg, \"FU::Pg::conn\")) $var = (fupg_conn *)SvIVX(SvRV($arg));
|
||||||
else fu_confess(\"invalid connection object\");
|
else fu_confess(\"invalid connection object\");
|
||||||
|
|
||||||
FUPG_TXN
|
FUPG_TXN
|
||||||
if (sv_derived_from($arg, \"FU::PG::txn\")) $var = (fupg_txn *)SvIVX(SvRV($arg));
|
if (sv_derived_from($arg, \"FU::Pg::txn\")) $var = (fupg_txn *)SvIVX(SvRV($arg));
|
||||||
else fu_confess(\"invalid transaction object\");
|
else fu_confess(\"invalid transaction object\");
|
||||||
|
|
||||||
FUPG_ST
|
FUPG_ST
|
||||||
if (sv_derived_from($arg, \"FU::PG::st\")) $var = (fupg_st *)SvIVX(SvRV($arg));
|
if (sv_derived_from($arg, \"FU::Pg::st\")) $var = (fupg_st *)SvIVX(SvRV($arg));
|
||||||
else fu_confess(\"invalid statement object\");
|
else fu_confess(\"invalid statement object\");
|
||||||
#"
|
#"
|
||||||
EOT
|
EOT
|
||||||
|
|
@ -75,7 +75,7 @@ void json_parse(SV *val, ...)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
MODULE = FU PACKAGE = FU::PG
|
MODULE = FU PACKAGE = FU::Pg
|
||||||
|
|
||||||
void _load_libpq()
|
void _load_libpq()
|
||||||
CODE:
|
CODE:
|
||||||
|
|
@ -91,7 +91,7 @@ void connect(const char *pkg, const char *conninfo)
|
||||||
ST(0) = fupg_connect(aTHX_ conninfo);
|
ST(0) = fupg_connect(aTHX_ conninfo);
|
||||||
|
|
||||||
|
|
||||||
MODULE = FU PACKAGE = FU::PG::conn
|
MODULE = FU PACKAGE = FU::Pg::conn
|
||||||
|
|
||||||
void server_version(fupg_conn *c)
|
void server_version(fupg_conn *c)
|
||||||
CODE:
|
CODE:
|
||||||
|
|
@ -109,9 +109,9 @@ void status(fupg_conn *c)
|
||||||
|
|
||||||
void cache(fupg_conn *x, ...)
|
void cache(fupg_conn *x, ...)
|
||||||
ALIAS:
|
ALIAS:
|
||||||
FU::PG::conn::text_params = FUPG_TEXT_PARAMS
|
FU::Pg::conn::text_params = FUPG_TEXT_PARAMS
|
||||||
FU::PG::conn::text_results = FUPG_TEXT_RESULTS
|
FU::Pg::conn::text_results = FUPG_TEXT_RESULTS
|
||||||
FU::PG::conn::text = FUPG_TEXT
|
FU::Pg::conn::text = FUPG_TEXT
|
||||||
CODE:
|
CODE:
|
||||||
FUPG_STFLAGS;
|
FUPG_STFLAGS;
|
||||||
|
|
||||||
|
|
@ -139,7 +139,7 @@ void q(fupg_conn *c, SV *sv, ...)
|
||||||
ST(0) = fupg_q(aTHX_ c, c->stflags, SvPVutf8_nolen(sv), ax, items);
|
ST(0) = fupg_q(aTHX_ c, c->stflags, SvPVutf8_nolen(sv), ax, items);
|
||||||
|
|
||||||
|
|
||||||
MODULE = FU PACKAGE = FU::PG::txn
|
MODULE = FU PACKAGE = FU::Pg::txn
|
||||||
|
|
||||||
void DESTROY(fupg_txn *t)
|
void DESTROY(fupg_txn *t)
|
||||||
CODE:
|
CODE:
|
||||||
|
|
@ -147,9 +147,9 @@ void DESTROY(fupg_txn *t)
|
||||||
|
|
||||||
void cache(fupg_txn *x, ...)
|
void cache(fupg_txn *x, ...)
|
||||||
ALIAS:
|
ALIAS:
|
||||||
FU::PG::txn::text_params = FUPG_TEXT_PARAMS
|
FU::Pg::txn::text_params = FUPG_TEXT_PARAMS
|
||||||
FU::PG::txn::text_results = FUPG_TEXT_RESULTS
|
FU::Pg::txn::text_results = FUPG_TEXT_RESULTS
|
||||||
FU::PG::txn::text = FUPG_TEXT
|
FU::Pg::txn::text = FUPG_TEXT
|
||||||
CODE:
|
CODE:
|
||||||
FUPG_STFLAGS;
|
FUPG_STFLAGS;
|
||||||
|
|
||||||
|
|
@ -183,20 +183,20 @@ void q(fupg_txn *t, SV *sv, ...)
|
||||||
ST(0) = fupg_q(aTHX_ t->conn, t->stflags, SvPVutf8_nolen(sv), ax, items);
|
ST(0) = fupg_q(aTHX_ t->conn, t->stflags, SvPVutf8_nolen(sv), ax, items);
|
||||||
|
|
||||||
|
|
||||||
MODULE = FU PACKAGE = FU::PG::st
|
MODULE = FU PACKAGE = FU::Pg::st
|
||||||
|
|
||||||
void cache(fupg_st *x, ...)
|
void cache(fupg_st *x, ...)
|
||||||
ALIAS:
|
ALIAS:
|
||||||
FU::PG::st::text_params = FUPG_TEXT_PARAMS
|
FU::Pg::st::text_params = FUPG_TEXT_PARAMS
|
||||||
FU::PG::st::text_results = FUPG_TEXT_RESULTS
|
FU::Pg::st::text_results = FUPG_TEXT_RESULTS
|
||||||
FU::PG::st::text = FUPG_TEXT
|
FU::Pg::st::text = FUPG_TEXT
|
||||||
CODE:
|
CODE:
|
||||||
FUPG_STFLAGS;
|
FUPG_STFLAGS;
|
||||||
|
|
||||||
void params(fupg_st *st)
|
void param_types(fupg_st *st)
|
||||||
CODE:
|
CODE:
|
||||||
FUPG_ST_COOKIE;
|
FUPG_ST_COOKIE;
|
||||||
ST(0) = fupg_st_params(aTHX_ st);
|
ST(0) = fupg_st_param_types(aTHX_ st);
|
||||||
|
|
||||||
void columns(fupg_st *st)
|
void columns(fupg_st *st)
|
||||||
CODE:
|
CODE:
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
package FU::PG 0.1;
|
package FU::Pg 0.1;
|
||||||
use v5.36;
|
use v5.36;
|
||||||
use FU::XS;
|
use FU::XS;
|
||||||
|
|
||||||
_load_libpq();
|
_load_libpq();
|
||||||
|
|
||||||
package FU::PG::conn {
|
package FU::Pg::conn {
|
||||||
sub lib_version { FU::PG::lib_version() }
|
sub lib_version { FU::Pg::lib_version() }
|
||||||
};
|
};
|
||||||
|
|
||||||
package FU::PG::error {
|
package FU::Pg::error {
|
||||||
use overload '""' => sub($e, @) { $e->{full_message} };
|
use overload '""' => sub($e, @) { $e->{full_message} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,15 +17,16 @@ __END__
|
||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
|
||||||
FU::PG - Another PostgreSQL client module
|
FU::Pg - The Ultimate (synchronous) Interface to PostgreSQL
|
||||||
|
|
||||||
=head1 SYNOPSYS
|
=head1 SYNOPSYS
|
||||||
|
|
||||||
my $conn = FU::PG->connect("dbname=test user=test password=nottest");
|
my $conn = FU::Pg->connect("dbname=test user=test password=nottest");
|
||||||
|
|
||||||
$conn->exec('CREATE TABLE books (id SERIAL, title text)');
|
$conn->exec('CREATE TABLE books (id SERIAL, title text, read bool)');
|
||||||
|
|
||||||
$conn->q('INSERT INTO books (title) VALUES ($1)', 'Revelation Space')->exec;
|
$conn->q('INSERT INTO books (title) VALUES ($1)', 'Revelation Space')->exec;
|
||||||
|
$conn->q('INSERT INTO books (title) VALUES ($1)', 'The Invincible')->exec;
|
||||||
|
|
||||||
for my ($id, $title) ($conn->q('SELECT * FROM books')->flat->@*) {
|
for my ($id, $title) ($conn->q('SELECT * FROM books')->flat->@*) {
|
||||||
print "$id: $title\n";
|
print "$id: $title\n";
|
||||||
|
|
@ -33,33 +34,17 @@ FU::PG - Another PostgreSQL client module
|
||||||
|
|
||||||
=head1 DESCRIPTION
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
FU::PG is a PostgreSQL client module that (attempts) to set itself apart from
|
FU::Pg is a client module for PostgreSQL with a convenient high-level API and
|
||||||
the existing alternatives by offering the following features:
|
support for flexible and complex type conversions. This module interfaces
|
||||||
|
directly with C<libpq>.
|
||||||
=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
|
=head2 Connection setup
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
|
||||||
=item B<< FU::PG->connect($string) >>
|
=item B<< FU::Pg->connect($string) >>
|
||||||
|
|
||||||
Connect to the PostgreSQL server and return a new C<FU::PG::conn> object.
|
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
|
C<$string> can either be in key=value format or a URI, refer to L<the
|
||||||
PostgreSQL
|
PostgreSQL
|
||||||
documentation|https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>
|
documentation|https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>
|
||||||
|
|
@ -75,7 +60,7 @@ C<$major * 10000 + $minor>. For example, returns 170002 for PostgreSQL 17.2.
|
||||||
=item B<< $conn->lib_version >>
|
=item B<< $conn->lib_version >>
|
||||||
|
|
||||||
Returns the libpq version in the same format as the C<server_version> method.
|
Returns the libpq version in the same format as the C<server_version> method.
|
||||||
Also available directly as C<FU::PG::lib_version()>.
|
Also available directly as C<FU::Pg::lib_version()>.
|
||||||
|
|
||||||
=item B<< $conn->status >>
|
=item B<< $conn->status >>
|
||||||
|
|
||||||
|
|
@ -135,7 +120,7 @@ attempts to use C<$conn> throw an error.
|
||||||
|
|
||||||
Execute one or more SQL commands, separated by a semicolon. Returns the number
|
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
|
of rows affected by the last statement or I<undef> if that information is not
|
||||||
available for the given command (like `CREATE TABLE`).
|
available for the given command (like with C<CREATE TABLE>).
|
||||||
|
|
||||||
=item B<< $conn->q($sql, @params) >>
|
=item B<< $conn->q($sql, @params) >>
|
||||||
|
|
||||||
|
|
@ -182,19 +167,16 @@ Statement objects can be inspected with the following two methods:
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
|
||||||
=item B<< $st->params >>
|
=item B<< $st->param_types >>
|
||||||
|
|
||||||
Returns an arrayref of hashrefs describing each parameter in the given C<$sql>
|
Returns an arrayref of integers indicating the type (as I<oid>) of each
|
||||||
string. Each parameter only has a single key for now: C<oid>, indicating the
|
parameter in the given C<$sql> string. Example:
|
||||||
type Oid. Example:
|
|
||||||
|
|
||||||
my $params = $conn->q('SELECT id FROM books WHERE id = $1')->params;
|
my $oids = $conn->q('SELECT id FROM books WHERE id = $1 AND title = $2')->param_types;
|
||||||
# $params = [ { oid => 23 } ]
|
# $oids = [23,25]
|
||||||
|
|
||||||
my $params = $conn->q('SELECT id FROM books')->params;
|
my $oids = $conn->q('SELECT id FROM books')->params;
|
||||||
# $params = []
|
# $oids = []
|
||||||
|
|
||||||
I<TODO: Resolve the oid to a more human-readable type>
|
|
||||||
|
|
||||||
=item B<< $st->columns >>
|
=item B<< $st->columns >>
|
||||||
|
|
||||||
|
|
@ -220,34 +202,114 @@ how you'd like to obtain the results:
|
||||||
Execute the query and return the number of rows affected. Similar to C<<
|
Execute the query and return the number of rows affected. Similar to C<<
|
||||||
$conn->exec >>.
|
$conn->exec >>.
|
||||||
|
|
||||||
|
my $v = $conn->q('UPDATE books SET read = true WHERE id = 1')->exec;
|
||||||
|
# $v = 1
|
||||||
|
|
||||||
=item B<< $st->val >>
|
=item B<< $st->val >>
|
||||||
|
|
||||||
Return the first column of the first row. Throws an error if the query does not
|
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>
|
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>.
|
if no rows are returned or if its value is I<NULL>.
|
||||||
|
|
||||||
|
my $v = $conn->q('SELECT COUNT(*) FROM books')->val;
|
||||||
|
# $v = 2
|
||||||
|
|
||||||
=item B<< $st->rowl >>
|
=item B<< $st->rowl >>
|
||||||
|
|
||||||
Return the first row as a list. Throws an error if the query does not return
|
Return the first row as a list. Throws an error if the query does not return
|
||||||
exactly one row.
|
exactly one row.
|
||||||
|
|
||||||
|
my($id, $title) = $conn->q('SELECT id, title FROM books LIMIT 1')->rowl;
|
||||||
|
# ($id, $title) = (1, 'Revelation Space');
|
||||||
|
|
||||||
=item B<< $st->rowa >>
|
=item B<< $st->rowa >>
|
||||||
|
|
||||||
Return the first row as an arrayref, equivalent to C<< [$st->rowl] >> but
|
Return the first row as an arrayref, equivalent to C<< [$st->rowl] >> but
|
||||||
probably slightly more efficient.
|
might be slightly more efficient.
|
||||||
|
|
||||||
|
my $row = $conn->q('SELECT id, title FROM books LIMIT 1')->rowa;
|
||||||
|
# $row = [1, 'Revelation Space'];
|
||||||
|
|
||||||
=item B<< $st->rowh >>
|
=item B<< $st->rowh >>
|
||||||
|
|
||||||
Return the first row as a hashref. Also throws an error if the query returns
|
Return the first row as a hashref. Also throws an error if the query returns
|
||||||
multiple columns with the same name.
|
multiple columns with the same name.
|
||||||
|
|
||||||
|
my $row = $conn->q('SELECT id, title FROM books LIMIT 1')->rowh;
|
||||||
|
# $row = { id => 1, title => 'Revelation Space' };
|
||||||
|
|
||||||
|
=item B<< $st->alla >>
|
||||||
|
|
||||||
|
Return all rows as an arrayref of arrayrefs.
|
||||||
|
|
||||||
|
my $data = $conn->q('SELECT id, title FROM books')->alla;
|
||||||
|
# $data = [
|
||||||
|
# [ 1, 'Revelation Space' ],
|
||||||
|
# [ 2, 'The Invincible' ],
|
||||||
|
# ];
|
||||||
|
|
||||||
|
=item B<< $st->allh >>
|
||||||
|
|
||||||
|
Return all rows as an arrayref of hashrefs. Throws an error if the query
|
||||||
|
returns multiple columns with the same name.
|
||||||
|
|
||||||
|
my $data = $conn->q('SELECT id, title FROM books')->allh;
|
||||||
|
# $data = [
|
||||||
|
# { id => 1, title => 'Revelation Space' },
|
||||||
|
# { id => 2, title => 'The Invincible' },
|
||||||
|
# ];
|
||||||
|
|
||||||
|
=item B<< $st->flat >>
|
||||||
|
|
||||||
|
Return an arrayref with all rows flattened.
|
||||||
|
|
||||||
|
my $data = $conn->q('SELECT id, title FROM books')->flat;
|
||||||
|
# $data = [
|
||||||
|
# 1, 'Revelation Space',
|
||||||
|
# 2, 'The Invincible',
|
||||||
|
# ];
|
||||||
|
|
||||||
|
=item B<< $st->kvv >>
|
||||||
|
|
||||||
|
Return a hashref where the first result column is used as key and the second
|
||||||
|
column as value. If the query only returns a single column, C<true> is used as
|
||||||
|
value instead. An error is thrown if the query returns 3 or more columns.
|
||||||
|
|
||||||
|
my $data = $conn->q('SELECT id, title FROM books')->kvv;
|
||||||
|
# $data = {
|
||||||
|
# 1 => 'Revelation Space',
|
||||||
|
# 2 => 'The Invincible',
|
||||||
|
# };
|
||||||
|
|
||||||
|
=item B<< $st->kva >>
|
||||||
|
|
||||||
|
Return a hashref where the first result column is used as key and the remaining
|
||||||
|
columns are stored as arrayref.
|
||||||
|
|
||||||
|
my $data = $conn->q('SELECT id, title, read FROM books')->kva;
|
||||||
|
# $data = {
|
||||||
|
# 1 => [ 'Revelation Space', true ],
|
||||||
|
# 2 => [ 'The Invincible', false ],
|
||||||
|
# };
|
||||||
|
|
||||||
|
=item B<< $st->kvh >>
|
||||||
|
|
||||||
|
Return a hashref where the first result column is used as key and the remaining
|
||||||
|
columns are stored as hashref.
|
||||||
|
|
||||||
|
my $data = $conn->q('SELECT id, title, read FROM books')->kvh;
|
||||||
|
# $data = {
|
||||||
|
# 1 => { title => 'Revelation Space', read => true },
|
||||||
|
# 2 => { title => 'The Invincible', read => false },
|
||||||
|
# };
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
The only time you actually need to assign a statement object to a variable is
|
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
|
when you want to inspect C<param_types> or C<columns>, in all other cases you
|
||||||
chain the methods for more concise code. For example:
|
can chain the methods for more concise code. For example:
|
||||||
|
|
||||||
my @cols = $conn->q('SELECT a, b FROM table')->cache(0)->text->rowa;
|
my $data = $conn->q('SELECT a, b FROM table')->cache(0)->text->alla;
|
||||||
|
|
||||||
|
|
||||||
=head2 Transactions
|
=head2 Transactions
|
||||||
|
|
@ -319,7 +381,8 @@ connection or any already existing subtransactions.
|
||||||
=item B<< $txn->txn >>
|
=item B<< $txn->txn >>
|
||||||
|
|
||||||
Create a subtransaction within the current transaction. A subtransaction works
|
Create a subtransaction within the current transaction. A subtransaction works
|
||||||
exactly the same as a top-level transaction.
|
exactly the same as a top-level transaction, except any changes remain
|
||||||
|
invisible to other sessions until the top-level transaction has been committed.
|
||||||
|
|
||||||
=item B<< $txn->status >>
|
=item B<< $txn->status >>
|
||||||
|
|
||||||
|
|
@ -371,6 +434,10 @@ Just don't try to use transaction objects and manual transaction commands at
|
||||||
the same time, that won't end well.
|
the same time, that won't end well.
|
||||||
|
|
||||||
|
|
||||||
|
=head2 Formats and Types
|
||||||
|
|
||||||
|
I<TODO>
|
||||||
|
|
||||||
=head2 Errors
|
=head2 Errors
|
||||||
|
|
||||||
I<TODO>
|
I<TODO>
|
||||||
|
|
@ -391,7 +458,7 @@ 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
|
`client_encoding=utf8` as part of the connection string or manually switching
|
||||||
to it after C<connect()> is always safe:
|
to it after C<connect()> is always safe:
|
||||||
|
|
||||||
my $conn = FU::PG->connect('');
|
my $conn = FU::Pg->connect('');
|
||||||
$conn->exec('SET client_encoding=utf8');
|
$conn->exec('SET client_encoding=utf8');
|
||||||
|
|
||||||
=item * Only works with blocking (synchronous) calls, not very suitable for use
|
=item * Only works with blocking (synchronous) calls, not very suitable for use
|
||||||
|
|
@ -400,7 +467,30 @@ low-latency connection with the Postgres server.
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
Missing features (for now): I<pretty much everything>.
|
Missing features:
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item COPY support
|
||||||
|
|
||||||
|
I hope to implement this someday.
|
||||||
|
|
||||||
|
=item LISTEN support
|
||||||
|
|
||||||
|
Would be nice to have, most likely doable without going full async.
|
||||||
|
|
||||||
|
=item Asynchronous calls
|
||||||
|
|
||||||
|
Probably won't happen. Perl's async story is slightly awkward in general, and
|
||||||
|
fully supporting async operation might require a fundamental redesign of how
|
||||||
|
this module works. It certainly won't I<simplify> the implementation.
|
||||||
|
|
||||||
|
=item Pipelining
|
||||||
|
|
||||||
|
I have some ideas for an API, but doubt I'll ever implement it. Suffers from
|
||||||
|
the same awkwardness and complexity as asynchronous calls.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
|
||||||
=head1 SEE ALSO
|
=head1 SEE ALSO
|
||||||
|
|
@ -414,13 +504,12 @@ than this module, but type conversions may leave things to be desired.
|
||||||
|
|
||||||
=item L<Pg::PQ>
|
=item L<Pg::PQ>
|
||||||
|
|
||||||
A thin wrapper around libpq. Lacks many higher-level conveniences and does not
|
Thin wrapper around libpq. Lacks many higher-level conveniences and doesn't do
|
||||||
support binary transfers (at the time of writing, but then again there's little
|
any type conversions for you.
|
||||||
benefit in dealing with the binary format in pure perl anyway).
|
|
||||||
|
|
||||||
=item L<DBIx::Simple>
|
=item L<DBIx::Simple>
|
||||||
|
|
||||||
A popular DBI wrapper with some API conveniences. I may have taken some
|
Popular DBI wrapper with some API conveniences. I may have taken some
|
||||||
inspiration from it in the design of this module's API.
|
inspiration from it in the design of this module's API.
|
||||||
|
|
||||||
=back
|
=back
|
||||||
13
c/pgconn.c
13
c/pgconn.c
|
|
@ -37,7 +37,7 @@ static SV *fupg_conn_errsv(PGconn *conn, const char *action) {
|
||||||
hv_stores(hv, "action", newSVpv(action, 0));
|
hv_stores(hv, "action", newSVpv(action, 0));
|
||||||
hv_stores(hv, "severity", newSVpvs("FATAL")); /* Connection-related errors are always fatal */
|
hv_stores(hv, "severity", newSVpvs("FATAL")); /* Connection-related errors are always fatal */
|
||||||
hv_stores(hv, "message", newSVpv(PQerrorMessage(conn), 0));
|
hv_stores(hv, "message", newSVpv(PQerrorMessage(conn), 0));
|
||||||
return fu_croak_hv(hv, "FU::PG::error", "FATAL: %s", PQerrorMessage(conn));
|
return fu_croak_hv(hv, "FU::Pg::error", "FATAL: %s", PQerrorMessage(conn));
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((noreturn))
|
__attribute__((noreturn))
|
||||||
|
|
@ -89,8 +89,8 @@ static void fupg_result_croak(PGresult *r, const char *action, const char *query
|
||||||
|
|
||||||
PQclear(r);
|
PQclear(r);
|
||||||
croak_sv(verbose
|
croak_sv(verbose
|
||||||
? fu_croak_hv(hv, "FU::PG::error", "%s", SvPV_nolen(*hv_fetchs(hv, "verbose_message", 0)))
|
? fu_croak_hv(hv, "FU::Pg::error", "%s", SvPV_nolen(*hv_fetchs(hv, "verbose_message", 0)))
|
||||||
: fu_croak_hv(hv, "FU::PG::error", "%s: %s",
|
: fu_croak_hv(hv, "FU::Pg::error", "%s: %s",
|
||||||
SvPV_nolen(*hv_fetchs(hv, "severity", 0)),
|
SvPV_nolen(*hv_fetchs(hv, "severity", 0)),
|
||||||
SvPV_nolen(*hv_fetchs(hv, "message", 0))
|
SvPV_nolen(*hv_fetchs(hv, "message", 0))
|
||||||
)
|
)
|
||||||
|
|
@ -121,6 +121,7 @@ static void fupg_exec_ok(pTHX_ fupg_conn *c, const char *sql) {
|
||||||
/* Connection & transaction handling */
|
/* Connection & transaction handling */
|
||||||
|
|
||||||
static SV *fupg_connect(pTHX_ const char *str) {
|
static SV *fupg_connect(pTHX_ const char *str) {
|
||||||
|
if (!PQconnectdb) fupg_load();
|
||||||
PGconn *conn = PQconnectdb(str);
|
PGconn *conn = PQconnectdb(str);
|
||||||
if (PQstatus(conn) != CONNECTION_OK) {
|
if (PQstatus(conn) != CONNECTION_OK) {
|
||||||
SV *sv = fupg_conn_errsv(conn, "connect");
|
SV *sv = fupg_conn_errsv(conn, "connect");
|
||||||
|
|
@ -135,7 +136,7 @@ static SV *fupg_connect(pTHX_ const char *str) {
|
||||||
c->ntypes = 0;
|
c->ntypes = 0;
|
||||||
c->types = NULL;
|
c->types = NULL;
|
||||||
fustr_init(&c->buf, NULL, SIZE_MAX);
|
fustr_init(&c->buf, NULL, SIZE_MAX);
|
||||||
return fu_selfobj(c, "FU::PG::conn");
|
return fu_selfobj(c, "FU::Pg::conn");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *fupg_conn_status(fupg_conn *c) {
|
static const char *fupg_conn_status(fupg_conn *c) {
|
||||||
|
|
@ -169,7 +170,7 @@ static SV *fupg_conn_txn(pTHX_ fupg_conn *c) {
|
||||||
t->stflags = c->stflags;
|
t->stflags = c->stflags;
|
||||||
strcpy(t->rollback_cmd, "ROLLBACK");
|
strcpy(t->rollback_cmd, "ROLLBACK");
|
||||||
SvREFCNT_inc(c->self);
|
SvREFCNT_inc(c->self);
|
||||||
return fu_selfobj(t, "FU::PG::txn");
|
return fu_selfobj(t, "FU::Pg::txn");
|
||||||
}
|
}
|
||||||
|
|
||||||
static SV *fupg_txn_txn(pTHX_ fupg_txn *t) {
|
static SV *fupg_txn_txn(pTHX_ fupg_txn *t) {
|
||||||
|
|
@ -185,7 +186,7 @@ static SV *fupg_txn_txn(pTHX_ fupg_txn *t) {
|
||||||
n->stflags = t->stflags;
|
n->stflags = t->stflags;
|
||||||
snprintf(n->rollback_cmd, sizeof(n->rollback_cmd), "ROLLBACK TO SAVEPOINT fupg_%"UVuf, cookie);
|
snprintf(n->rollback_cmd, sizeof(n->rollback_cmd), "ROLLBACK TO SAVEPOINT fupg_%"UVuf, cookie);
|
||||||
SvREFCNT_inc(t->self);
|
SvREFCNT_inc(t->self);
|
||||||
return fu_selfobj(n, "FU::PG::txn");
|
return fu_selfobj(n, "FU::Pg::txn");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *fupg_txn_status(fupg_txn *t) {
|
static const char *fupg_txn_status(fupg_txn *t) {
|
||||||
|
|
|
||||||
17
c/pgst.c
17
c/pgst.c
|
|
@ -56,7 +56,7 @@ static SV *fupg_q(pTHX_ fupg_conn *c, int stflags, const char *query, I32 ax, I3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fu_selfobj(st, "FU::PG::st");
|
return fu_selfobj(st, "FU::Pg::st");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fupg_st_destroy(fupg_st *st) {
|
static void fupg_st_destroy(fupg_st *st) {
|
||||||
|
|
@ -124,15 +124,12 @@ static void fupg_st_prepare(pTHX_ fupg_st *st) {
|
||||||
PQclear(sync);
|
PQclear(sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
static SV *fupg_st_params(pTHX_ fupg_st *st) {
|
static SV *fupg_st_param_types(pTHX_ fupg_st *st) {
|
||||||
fupg_st_prepare(aTHX_ st);
|
fupg_st_prepare(aTHX_ st);
|
||||||
int i, nparams = PQnparams(st->describe);
|
int i, nparams = PQnparams(st->describe);
|
||||||
AV *av = newAV_alloc_x(nparams);
|
AV *av = newAV_alloc_x(nparams);
|
||||||
for (i=0; i<nparams; i++) {
|
for (i=0; i<nparams; i++)
|
||||||
HV *hv = newHV();
|
av_push_simple(av, newSViv(PQparamtype(st->describe, i)));
|
||||||
hv_stores(hv, "oid", newSViv(PQparamtype(st->describe, i)));
|
|
||||||
av_push_simple(av, newRV_noinc((SV *)hv));
|
|
||||||
}
|
|
||||||
return sv_2mortal(newRV_noinc((SV *)av));
|
return sv_2mortal(newRV_noinc((SV *)av));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,10 +253,8 @@ static void fupg_st_check_dupcols(pTHX_ fupg_st *st, int start) {
|
||||||
for (i=start; i<nfields; i++) {
|
for (i=start; i<nfields; i++) {
|
||||||
const char *key = PQfname(r, i);
|
const char *key = PQfname(r, i);
|
||||||
int len = -strlen(key);
|
int len = -strlen(key);
|
||||||
if (hv_exists(hv, key, len)) {
|
if (hv_exists(hv, key, len))
|
||||||
SvREFCNT_dec((SV *)hv);
|
|
||||||
fu_confess("Query returns multiple columns with the same name ('%s')", key);
|
fu_confess("Query returns multiple columns with the same name ('%s')", key);
|
||||||
}
|
|
||||||
hv_store(hv, key, len, &PL_sv_yes, 0);
|
hv_store(hv, key, len, &PL_sv_yes, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -359,7 +354,7 @@ static SV *fupg_st_allh(pTHX_ fupg_st *st) {
|
||||||
static SV *fupg_st_flat(pTHX_ fupg_st *st) {
|
static SV *fupg_st_flat(pTHX_ fupg_st *st) {
|
||||||
fupg_st_execute(aTHX_ st);
|
fupg_st_execute(aTHX_ st);
|
||||||
int i, j, nrows = PQntuples(st->result);
|
int i, j, nrows = PQntuples(st->result);
|
||||||
AV *av = newAV_alloc_x(nrows);
|
AV *av = newAV_alloc_x(nrows * st->nfields);
|
||||||
SV *sv = sv_2mortal(newRV_noinc((SV *)av));
|
SV *sv = sv_2mortal(newRV_noinc((SV *)av));
|
||||||
for (i=0; i<nrows; i++) {
|
for (i=0; i<nrows; i++) {
|
||||||
for (j=0; j<st->nfields; j++)
|
for (j=0; j<st->nfields; j++)
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
use v5.36;
|
use v5.36;
|
||||||
use Test::More;
|
use Test::More;
|
||||||
|
|
||||||
plan skip_all => $@ if !eval { require FU::PG; } && $@ =~ /Unable to load libpq/;
|
plan skip_all => $@ if !eval { require FU::Pg; } && $@ =~ /Unable to load libpq/;
|
||||||
die $@ if $@;
|
die $@ if $@;
|
||||||
plan skip_all => 'Please set FU_TEST_DB to a PostgreSQL connection string to run these tests' if !$ENV{FU_TEST_DB};
|
plan skip_all => 'Please set FU_TEST_DB to a PostgreSQL connection string to run these tests' if !$ENV{FU_TEST_DB};
|
||||||
|
|
||||||
sub okerr($sev, $act, $msg) {
|
sub okerr($sev, $act, $msg) {
|
||||||
is ref $@, 'FU::PG::error';
|
is ref $@, 'FU::Pg::error';
|
||||||
is $@->{severity}, $sev;
|
is $@->{severity}, $sev;
|
||||||
is $@->{action}, $act;
|
is $@->{action}, $act;
|
||||||
like "$@", $msg;
|
like "$@", $msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok !eval { FU::PG->connect("invalid") };
|
ok !eval { FU::Pg->connect("invalid") };
|
||||||
okerr FATAL => connect => qr/missing "=" after "invalid"/;
|
okerr FATAL => connect => qr/missing "=" after "invalid"/;
|
||||||
|
|
||||||
ok FU::PG::lib_version() > 100000;
|
ok FU::Pg::lib_version() > 100000;
|
||||||
|
|
||||||
my $conn = FU::PG->connect($ENV{FU_TEST_DB})->text;
|
my $conn = FU::Pg->connect($ENV{FU_TEST_DB})->text;
|
||||||
$conn->_debug_trace(0);
|
$conn->_debug_trace(0);
|
||||||
|
|
||||||
is ref $conn, 'FU::PG::conn';
|
is ref $conn, 'FU::Pg::conn';
|
||||||
ok $conn->server_version > 100000;
|
ok $conn->server_version > 100000;
|
||||||
is $conn->lib_version, FU::PG::lib_version();
|
is $conn->lib_version, FU::Pg::lib_version();
|
||||||
is $conn->status, 'idle';
|
is $conn->status, 'idle';
|
||||||
|
|
||||||
subtest '$conn->exec', sub {
|
subtest '$conn->exec', sub {
|
||||||
|
|
@ -35,7 +35,7 @@ subtest '$conn->exec', sub {
|
||||||
ok !defined $conn->exec('');
|
ok !defined $conn->exec('');
|
||||||
is $conn->exec('SELECT 1'), 1;
|
is $conn->exec('SELECT 1'), 1;
|
||||||
|
|
||||||
ok !eval { $conn->q('SELEXT')->params; };
|
ok !eval { $conn->q('SELEXT')->param_types; };
|
||||||
okerr ERROR => prepare => qr/syntax error/;
|
okerr ERROR => prepare => qr/syntax error/;
|
||||||
|
|
||||||
is $conn->exec('SET client_encoding=utf8'), undef;
|
is $conn->exec('SET client_encoding=utf8'), undef;
|
||||||
|
|
@ -45,7 +45,7 @@ subtest '$conn->exec', sub {
|
||||||
subtest '$st prepare & exec', sub {
|
subtest '$st prepare & exec', sub {
|
||||||
{
|
{
|
||||||
my $st = $conn->q('SELECT 1');
|
my $st = $conn->q('SELECT 1');
|
||||||
is_deeply $st->params, [];
|
is_deeply $st->param_types, [];
|
||||||
is_deeply $st->columns, [{ name => '?column?', oid => 23 }];
|
is_deeply $st->columns, [{ name => '?column?', oid => 23 }];
|
||||||
is $conn->exec('SELECT 1 FROM pg_prepared_statements'), 1;
|
is $conn->exec('SELECT 1 FROM pg_prepared_statements'), 1;
|
||||||
is $st->exec, 1;
|
is $st->exec, 1;
|
||||||
|
|
@ -55,7 +55,7 @@ subtest '$st prepare & exec', sub {
|
||||||
|
|
||||||
{
|
{
|
||||||
my $st = $conn->q("SELECT \$1::int AS a, \$2::char(5) AS \"\x{1F603}\"", 1, 2);
|
my $st = $conn->q("SELECT \$1::int AS a, \$2::char(5) AS \"\x{1F603}\"", 1, 2);
|
||||||
is_deeply $st->params, [ { oid => 23 }, { oid => 1042 } ];
|
is_deeply $st->param_types, [ 23, 1042 ];
|
||||||
is_deeply $st->columns, [
|
is_deeply $st->columns, [
|
||||||
{ oid => 23, name => 'a' },
|
{ oid => 23, name => 'a' },
|
||||||
{ oid => 1042, name => "\x{1F603}", typemod => 9 },
|
{ oid => 1042, name => "\x{1F603}", typemod => 9 },
|
||||||
|
|
@ -72,7 +72,7 @@ subtest '$st prepare & exec', sub {
|
||||||
like $@, qr/Statement expects 1 bind parameters but 0 were given/;
|
like $@, qr/Statement expects 1 bind parameters but 0 were given/;
|
||||||
|
|
||||||
# prepare + describe won't let us detect empty queries, hmm...
|
# prepare + describe won't let us detect empty queries, hmm...
|
||||||
is_deeply $conn->q('')->params, [];
|
is_deeply $conn->q('')->param_types, [];
|
||||||
is_deeply $conn->q('')->columns, [];
|
is_deeply $conn->q('')->columns, [];
|
||||||
|
|
||||||
ok !eval { $conn->q('')->exec; 1 };
|
ok !eval { $conn->q('')->exec; 1 };
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use v5.36;
|
use v5.36;
|
||||||
use Test::More;
|
use Test::More;
|
||||||
|
|
||||||
plan skip_all => $@ if !eval { require FU::PG; } && $@ =~ /Unable to load libpq/;
|
plan skip_all => $@ if !eval { require FU::Pg; } && $@ =~ /Unable to load libpq/;
|
||||||
die $@ if $@;
|
die $@ if $@;
|
||||||
plan skip_all => 'Please set FU_TEST_DB to a PostgreSQL connection string to run these tests' if !$ENV{FU_TEST_DB};
|
plan skip_all => 'Please set FU_TEST_DB to a PostgreSQL connection string to run these tests' if !$ENV{FU_TEST_DB};
|
||||||
|
|
||||||
my $conn = FU::PG->connect($ENV{FU_TEST_DB});
|
my $conn = FU::Pg->connect($ENV{FU_TEST_DB});
|
||||||
|
|
||||||
ok !eval { $conn->q('SELECT $1::aclitem', '')->exec; 1 };
|
ok !eval { $conn->q('SELECT $1::aclitem', '')->exec; 1 };
|
||||||
like $@, qr/Unable to send or receive/;
|
like $@, qr/Unable to send or receive/;
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ use Test::More;
|
||||||
no warnings 'experimental::builtin';
|
no warnings 'experimental::builtin';
|
||||||
use builtin qw/true false is_bool created_as_number/;
|
use builtin qw/true false is_bool created_as_number/;
|
||||||
|
|
||||||
plan skip_all => $@ if !eval { require FU::PG; } && $@ =~ /Unable to load libpq/;
|
plan skip_all => $@ if !eval { require FU::Pg; } && $@ =~ /Unable to load libpq/;
|
||||||
die $@ if $@;
|
die $@ if $@;
|
||||||
plan skip_all => 'Please set FU_TEST_DB to a PostgreSQL connection string to run these tests' if !$ENV{FU_TEST_DB};
|
plan skip_all => 'Please set FU_TEST_DB to a PostgreSQL connection string to run these tests' if !$ENV{FU_TEST_DB};
|
||||||
|
|
||||||
my $conn = FU::PG->connect($ENV{FU_TEST_DB});
|
my $conn = FU::Pg->connect($ENV{FU_TEST_DB});
|
||||||
$conn->_debug_trace(0);
|
$conn->_debug_trace(0);
|
||||||
|
|
||||||
# TODO: Test behavior of magic bind params
|
# TODO: Test behavior of magic bind params
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue