pg: Adventures in writing a new postgresql client

This commit is contained in:
Yorhel 2025-02-02 14:36:54 +01:00
parent 0d19ccdc1b
commit b242176071
7 changed files with 132 additions and 4 deletions

54
FU.xs
View file

@ -1,3 +1,5 @@
#include <dlfcn.h>
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
@ -6,12 +8,28 @@
#include "c/common.c"
#include "c/jsonfmt.c"
#include "c/jsonparse.c"
#include "c/pgconn.c"
MODULE = FU
PROTOTYPES: DISABLE
TYPEMAP: <<EOT
TYPEMAP
fupg_conn * FUPG_CONN
INPUT
FUPG_CONN
if (sv_derived_from($arg, \"FU::PG::conn\")) $var = (fupg_conn *)SvIVX(SvRV($arg));
else croak(\"invalid object\");
#"
EOT
MODULE = FU PACKAGE = FU::Util
PROTOTYPES: DISABLE
void json_format(SV *val, ...)
CODE:
ST(0) = fujson_fmt_xs(aTHX_ ax, items, val);
@ -19,3 +37,35 @@ void json_format(SV *val, ...)
void json_parse(SV *val, ...)
CODE:
ST(0) = fujson_parse_xs(aTHX_ ax, items, val);
MODULE = FU PACKAGE = FU::PG
void _load_libpq()
CODE:
if (!PQconnectdb) fupg_load();
int lib_version()
CODE:
RETVAL = PQlibVersion();
OUTPUT:
RETVAL
void connect(const char *pkg, const char *conninfo)
CODE:
(void)pkg;
ST(0) = sv_setref_pv(sv_newmortal(), "FU::PG::conn", fupg_connect(aTHX_ conninfo));
MODULE = FU PACKAGE = FU::PG::conn
int server_version(fupg_conn *c)
CODE:
RETVAL = PQserverVersion(c->conn);
OUTPUT:
RETVAL
void DESTROY(fupg_conn *c)
CODE:
fupg_destroy(c);

View file

@ -17,6 +17,9 @@ be a good measure". I've used these benchmarks to find and optimize hotspots in
FU, which in turn means these numbers may look better than they are in
real-world use.
B<DISCLAIMER#3:> Many of these benchmarks exists solely to test edge case
performance, these numbers are not representative for real-world use.
=head1 MODULE VERSIONS
The following module versions were used:
@ -39,7 +42,7 @@ The following module versions were used:
=head1 BENCHMARKS
=head2 JSON Formatting
=head2 JSON Parsing & Formatting
These benchmarks run on large-ish arrays with repeated values. JSON encoding is
sufficiently fast that Perl function calling overhead tends to dominate for

11
FU/PG.pm Normal file
View file

@ -0,0 +1,11 @@
package FU::PG 0.1;
use v5.36;
use FU::XS;
_load_libpq();
package FU::PG::conn {
sub lib_version { FU::PG::lib_version() }
};
1;

View file

@ -1,6 +1,7 @@
use ExtUtils::MakeMaker;
use Config;
os_unsupported if $^O eq 'MSWin32'; # I don't know on which OS'es the code will work exactly, but this one I can easily rule out.
os_unsupported if $Config{ivsize} < 8;
os_unsupported if $Config{usequadmath};

View file

@ -20,7 +20,6 @@ Things that may or may not happen:
- FU::HTTPServer / FU::FastCGI - Minimal libs to support the web framework.
- FU::JSON - JSON::{XS,PP,etc}-compatible wrapper around FU::Util's JSON functions? I prolly won't need this myself, but could be handy.
- FU::Log - Basic logger.
- FU::PG - PostgreSQL client with support for custom types and a small query builder.
- FU::Util additions: `uri_escape`, `VNDB::Util::query_encode`, `scrypt`, `urandom`.
- FU::Validate - TUWF::Validate & normalization with some improvements.
- FU::XML - TUWF::XMLXS with some improvements.

47
c/pgconn.c Normal file
View file

@ -0,0 +1,47 @@
typedef struct PGconn PGconn;
#define PG_FUNCS \
X(PQconnectdb, PGconn *, const char *) \
X(PQerrorMessage, char *, const PGconn *) \
X(PQfinish, void, PGconn *) \
X(PQlibVersion, int, void) \
X(PQserverVersion, int, const PGconn *) \
X(PQstatus, int, const PGconn *)
#define X(n, r, ...) static r (*n)(__VA_ARGS__);
PG_FUNCS
#undef X
static void fupg_load() {
void *handle = dlopen("libpq.so", RTLD_LAZY);
if (!handle) croak("Unable to load libpq: %s", dlerror());
#define X(n, ...) if (!(n = dlsym(handle, #n))) croak("Unable to load libpq: %s", dlerror());
PG_FUNCS
#undef X
}
#undef PG_FUNCS
typedef struct {
PGconn *conn;
} fupg_conn;
fupg_conn *fupg_connect(pTHX_ const char *str) {
SV *sv;
PGconn *conn = PQconnectdb(str);
if (PQstatus(conn) != 0) {
sv = newSVpvf("FU::PG connection error: %s", PQerrorMessage(conn));
PQfinish(conn);
croak_sv(sv);
}
fupg_conn *c = safecalloc(1, sizeof(fupg_conn));
c->conn = conn;
return c;
}
static void fupg_destroy(fupg_conn *c) {
PQfinish(c->conn);
safefree(c);
}

17
t/pgconnect.t Normal file
View file

@ -0,0 +1,17 @@
use v5.36;
use Test::More;
plan skip_all => $@ if !eval { require FU::PG; } && $@ =~ /Unable to load libpq/;
plan skip_all => 'Please set FU_TEST_DB to a PostgreSQL connection string to run these tests' if !$ENV{FU_TEST_DB};
ok !eval { FU::PG->connect("invalid") };
ok FU::PG::lib_version() > 100000;
my $conn = FU::PG->connect($ENV{FU_TEST_DB});
is ref $conn, 'FU::PG::conn';
ok $conn->server_version() > 100000;
is $conn->lib_version(), FU::PG::lib_version();
done_testing;