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 #define PERL_NO_GET_CONTEXT
#include "EXTERN.h" #include "EXTERN.h"
#include "perl.h" #include "perl.h"
@ -6,12 +8,28 @@
#include "c/common.c" #include "c/common.c"
#include "c/jsonfmt.c" #include "c/jsonfmt.c"
#include "c/jsonparse.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 MODULE = FU PACKAGE = FU::Util
PROTOTYPES: DISABLE
void json_format(SV *val, ...) void json_format(SV *val, ...)
CODE: CODE:
ST(0) = fujson_fmt_xs(aTHX_ ax, items, val); ST(0) = fujson_fmt_xs(aTHX_ ax, items, val);
@ -19,3 +37,35 @@ void json_format(SV *val, ...)
void json_parse(SV *val, ...) void json_parse(SV *val, ...)
CODE: CODE:
ST(0) = fujson_parse_xs(aTHX_ ax, items, val); 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 FU, which in turn means these numbers may look better than they are in
real-world use. 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 =head1 MODULE VERSIONS
The following module versions were used: The following module versions were used:
@ -39,7 +42,7 @@ The following module versions were used:
=head1 BENCHMARKS =head1 BENCHMARKS
=head2 JSON Formatting =head2 JSON Parsing & Formatting
These benchmarks run on large-ish arrays with repeated values. JSON encoding is These benchmarks run on large-ish arrays with repeated values. JSON encoding is
sufficiently fast that Perl function calling overhead tends to dominate for 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 ExtUtils::MakeMaker;
use Config; 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{ivsize} < 8;
os_unsupported if $Config{usequadmath}; 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::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::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::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::Util additions: `uri_escape`, `VNDB::Util::query_encode`, `scrypt`, `urandom`.
- FU::Validate - TUWF::Validate & normalization with some improvements. - FU::Validate - TUWF::Validate & normalization with some improvements.
- FU::XML - TUWF::XMLXS 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;