From b2421760716af830f2937c3b8b759d652ce75c9b Mon Sep 17 00:00:00 2001 From: Yorhel Date: Sun, 2 Feb 2025 14:36:54 +0100 Subject: [PATCH] pg: Adventures in writing a new postgresql client --- FU.xs | 54 +++++++++++++++++++++++++++++++++++++++++++++-- FU/Benchmarks.pod | 5 ++++- FU/PG.pm | 11 ++++++++++ Makefile.PL | 1 + README.md | 1 - c/pgconn.c | 47 +++++++++++++++++++++++++++++++++++++++++ t/pgconnect.t | 17 +++++++++++++++ 7 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 FU/PG.pm create mode 100644 c/pgconn.c create mode 100644 t/pgconnect.t diff --git a/FU.xs b/FU.xs index 7b7bb48..ee9dd19 100644 --- a/FU.xs +++ b/FU.xs @@ -1,3 +1,5 @@ +#include + #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: <conn); + OUTPUT: + RETVAL + +void DESTROY(fupg_conn *c) + CODE: + fupg_destroy(c); diff --git a/FU/Benchmarks.pod b/FU/Benchmarks.pod index 55f00db..651160f 100644 --- a/FU/Benchmarks.pod +++ b/FU/Benchmarks.pod @@ -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 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 diff --git a/FU/PG.pm b/FU/PG.pm new file mode 100644 index 0000000..7b8f10b --- /dev/null +++ b/FU/PG.pm @@ -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; diff --git a/Makefile.PL b/Makefile.PL index 29553b7..86cce2c 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -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}; diff --git a/README.md b/README.md index fbf4148..a3a7705 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/c/pgconn.c b/c/pgconn.c new file mode 100644 index 0000000..256ff56 --- /dev/null +++ b/c/pgconn.c @@ -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); +} diff --git a/t/pgconnect.t b/t/pgconnect.t new file mode 100644 index 0000000..da10a36 --- /dev/null +++ b/t/pgconnect.t @@ -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;