fu/FU.xs
Yorhel 9014e2900c Add FU::XMLWriter
And remove UTF-8 check in JSON writer. It honestly feels kind of silly
to do that validation there while I've never done similar validations in
any other output routines - including this XML writer.

FU::XMLWriter is a copy of TUWF::XMLXS with a bunch of improvements
applied: now uses refcounts to determine the current output instance,
auto-generates XS functions and has faster escaped string output -
inspired by the JSON writer.

TODO:
- Integrate into FU
- Do something with bool attribute values
- Benchmarks
- Should $content be optional for all tags? The reason they weren't in
  TUWF::XMLXS is because TUWF::XML supports opening tags without closing
  them, but that idea turned out to suck and isn't supported anymore.

This is hopefully the last XS module for the FU framework. The only C
code being written now should be bug fixes and extending FU::Pg with
some planned features. Already ended up with more C than I had
planned...
2025-02-19 12:01:24 +01:00

382 lines
8.4 KiB
Text

#include <stdio.h>
#include <errno.h>
#include <string.h> /* strerror() */
#include <arpa/inet.h> /* inet_ntop(), inet_ntoa() */
#include <sys/socket.h> /* fd passing */
#include <sys/un.h> /* fd passing */
#include <dlfcn.h> /* dlopen() etc */
#undef PERL_IMPLICIT_SYS
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#ifndef av_push_simple
#define av_push_simple av_push
#endif
#ifndef BOOL_INTERNALS_sv_isbool_true
#define BOOL_INTERNALS_sv_isbool_true(x) SvPVXtrue(x)
#endif
#include "c/khashl.h"
#include "c/common.c"
#include "c/jsonfmt.c"
#include "c/jsonparse.c"
#include "c/fdpass.c"
#include "c/fcgi.c"
#include "c/xmlwr.c"
#include "c/libpq.h"
#include "c/pgtypes.c"
#include "c/pgconn.c"
#include "c/pgst.c"
#define FUPG_CONN_COOKIE \
if (c->cookie) fu_confess("Invalid operation on the top-level connection while a transaction object exists")
#define FUPG_TXN_COOKIE \
if (!t->cookie) fu_confess("Invalid operation on a transaction that has already been marked as done"); \
if (t->cookie != t->conn->cookie) fu_confess("Invalid operation on transaction while a subtransaction object exists")
#define FUPG_ST_COOKIE \
if (st->cookie != st->conn->cookie) fu_confess("Invalid cross-transaction operation on statement object")
#define FUPG_STFLAGS do {\
if (!ix) ix = FUPG_CACHE;\
if (items == 1 || SvTRUE(ST(1))) x->stflags |= ix; \
else x->stflags &= ~ix; \
XSRETURN(1); \
} while(0)
MODULE = FU
PROTOTYPES: DISABLE
TYPEMAP: <<EOT
TYPEMAP
fufcgi * FUFCGI
fuxmlwr * FUXMLWR
fupg_conn * FUPG_CONN
fupg_txn * FUPG_TXN
fupg_st * FUPG_ST
INPUT
FUFCGI
if (sv_derived_from($arg, \"FU::fcgi\")) $var = (fufcgi *)SvIVX(SvRV($arg));
else fu_confess(\"invalid FastCGI object\");
FUXMLWR
if (sv_derived_from($arg, \"FU::XMLWriter\")) $var = (fuxmlwr *)SvIVX(SvRV($arg));
else fu_confess(\"invalid FU::XMLWriter object\");
FUPG_CONN
if (sv_derived_from($arg, \"FU::Pg::conn\")) $var = (fupg_conn *)SvIVX(SvRV($arg));
else fu_confess(\"invalid connection object\");
FUPG_TXN
if (sv_derived_from($arg, \"FU::Pg::txn\")) $var = (fupg_txn *)SvIVX(SvRV($arg));
else fu_confess(\"invalid transaction object\");
FUPG_ST
if (sv_derived_from($arg, \"FU::Pg::st\")) $var = (fupg_st *)SvIVX(SvRV($arg));
else fu_confess(\"invalid statement object\");
#"
EOT
MODULE = FU PACKAGE = FU::Util
void json_format(SV *val, ...)
CODE:
ST(0) = fujson_fmt_xs(aTHX_ ax, items, val);
void json_parse(SV *val, ...)
CODE:
ST(0) = fujson_parse_xs(aTHX_ ax, items, val);
void fdpass_send(int socket, int fd, SV *data)
CODE:
STRLEN buflen;
const char *buf = SvPVbyte(data, buflen);
ST(0) = sv_2mortal(newSViv(fufdpass_send(socket, fd, buf, buflen)));
void fdpass_recv(int socket, UV len)
CODE:
XSRETURN(fufdpass_recv(aTHX_ ax, socket, len));
MODULE = FU PACKAGE = FU::fcgi
void new(int fd, int maxproc)
CODE:
fufcgi *ctx = safemalloc(sizeof(*ctx));
ctx->fd = fd;
ctx->maxproc = maxproc;
ctx->reqid = ctx->keepconn = ctx->len = ctx->off = 0;
ST(0) = fu_selfobj(ctx, "FU::fcgi");
void read_req(fufcgi *ctx, SV *headers, SV *params)
CODE:
ST(0) = sv_2mortal(newSViv(fufcgi_read_req(aTHX_ ctx, headers, params)));
ctx->off = ctx->len = 0;
void keepalive(fufcgi *ctx)
CODE:
ST(0) = ctx->keepconn ? &PL_sv_yes : &PL_sv_no;
void print(fufcgi *ctx, SV *sv)
CODE:
STRLEN len;
const char *buf = SvPVbyte(sv, len);
fufcgi_print(ctx, buf, len);
void flush(fufcgi *ctx)
CODE:
fufcgi_done(ctx);
void DESTROY(fufcgi *ctx)
CODE:
safefree(ctx);
MODULE = FU PACKAGE = FU::Pg
void _load_libpq()
CODE:
if (!PQconnectdb) fupg_load();
void lib_version()
CODE:
XSRETURN_IV(PQlibVersion());
void connect(const char *pkg, const char *conninfo)
CODE:
(void)pkg;
ST(0) = fupg_connect(aTHX_ conninfo);
MODULE = FU PACKAGE = FU::Pg::conn
void server_version(fupg_conn *c)
CODE:
XSRETURN_IV(PQserverVersion(c->conn));
void _debug_trace(fupg_conn *c, bool on)
CODE:
if (on) PQtrace(c->conn, stderr);
else PQuntrace(c->conn);
ST(0) = c->self;
void status(fupg_conn *c)
CODE:
ST(0) = sv_2mortal(newSVpv(fupg_conn_status(c), 0));
void cache(fupg_conn *x, ...)
ALIAS:
FU::Pg::conn::text_params = FUPG_TEXT_PARAMS
FU::Pg::conn::text_results = FUPG_TEXT_RESULTS
FU::Pg::conn::text = FUPG_TEXT
CODE:
FUPG_STFLAGS;
void cache_size(fupg_conn *c, unsigned int n)
CODE:
c->prep_max = n;
fupg_prepared_prune(c);
XSRETURN(1);
void disconnect(fupg_conn *c)
CODE:
fupg_conn_disconnect(c);
void DESTROY(fupg_conn *c)
CODE:
fupg_conn_destroy(aTHX_ c);
void txn(fupg_conn *c)
CODE:
FUPG_CONN_COOKIE;
ST(0) = fupg_conn_txn(aTHX_ c);
void exec(fupg_conn *c, SV *sv)
CODE:
FUPG_CONN_COOKIE;
ST(0) = fupg_exec(aTHX_ c, SvPVutf8_nolen(sv));
void q(fupg_conn *c, SV *sv, ...)
CODE:
FUPG_CONN_COOKIE;
ST(0) = fupg_q(aTHX_ c, c->stflags, SvPVutf8_nolen(sv), ax, items);
MODULE = FU PACKAGE = FU::Pg::txn
void DESTROY(fupg_txn *t)
CODE:
fupg_txn_destroy(aTHX_ t);
void cache(fupg_txn *x, ...)
ALIAS:
FU::Pg::txn::text_params = FUPG_TEXT_PARAMS
FU::Pg::txn::text_results = FUPG_TEXT_RESULTS
FU::Pg::txn::text = FUPG_TEXT
CODE:
FUPG_STFLAGS;
void status(fupg_txn *t)
CODE:
ST(0) = sv_2mortal(newSVpv(fupg_txn_status(t), 0));
void txn(fupg_txn *t)
CODE:
FUPG_TXN_COOKIE;
ST(0) = fupg_txn_txn(aTHX_ t);
void commit(fupg_txn *t)
CODE:
FUPG_TXN_COOKIE;
fupg_txn_commit(t);
void rollback(fupg_txn *t)
CODE:
FUPG_TXN_COOKIE;
fupg_txn_rollback(t);
void exec(fupg_txn *t, SV *sv)
CODE:
FUPG_TXN_COOKIE;
ST(0) = fupg_exec(aTHX_ t->conn, SvPVutf8_nolen(sv));
void q(fupg_txn *t, SV *sv, ...)
CODE:
FUPG_TXN_COOKIE;
ST(0) = fupg_q(aTHX_ t->conn, t->stflags, SvPVutf8_nolen(sv), ax, items);
MODULE = FU PACKAGE = FU::Pg::st
void cache(fupg_st *x, ...)
ALIAS:
FU::Pg::st::text_params = FUPG_TEXT_PARAMS
FU::Pg::st::text_results = FUPG_TEXT_RESULTS
FU::Pg::st::text = FUPG_TEXT
CODE:
if (ix == 0 && x->prepared) fu_confess("Invalid attempt to change statement configuration after it has already been prepared or executed");
FUPG_STFLAGS;
void param_types(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_param_types(aTHX_ st);
void columns(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_columns(aTHX_ st);
void exec(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_exec(aTHX_ st);
void val(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_val(aTHX_ st);
void rowl(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
XSRETURN(fupg_st_rowl(aTHX_ st, ax));
void rowa(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_rowa(aTHX_ st);
void rowh(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_rowh(aTHX_ st);
void alla(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_alla(aTHX_ st);
void allh(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_allh(aTHX_ st);
void flat(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_flat(aTHX_ st);
void kvv(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_kvv(aTHX_ st);
void kva(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_kva(aTHX_ st);
void kvh(fupg_st *st)
CODE:
FUPG_ST_COOKIE;
ST(0) = fupg_st_kvh(aTHX_ st);
void DESTROY(fupg_st *st)
CODE:
fupg_st_destroy(aTHX_ st);
MODULE = FU PACKAGE = FU::XMLWriter
void _new()
CODE:
ST(0) = fuxmlwr_new(aTHX);
void _done(fuxmlwr *wr)
CODE:
ST(0) = fustr_done(&wr->out);
fustr_init(aTHX_ &wr->out, NULL, SIZE_MAX);
void lit_(SV *sv)
CODE:
if (!fuxmlwr_tail) fu_confess("No active FU::XMLWriter instance");
STRLEN len;
const char *buf = SvPVutf8(sv, len);
fustr_write(aTHX_ &fuxmlwr_tail->out, buf, len);
void txt_(SV *sv)
CODE:
if (!fuxmlwr_tail) fu_confess("No active FU::XMLWriter instance");
fuxmlwr_escape(aTHX_ fuxmlwr_tail, sv);
void tag_(SV *sv, ...)
CODE:
if (!fuxmlwr_tail) fu_confess("No active FU::XMLWriter instance");
STRLEN len;
const char *tagname = SvPV(sv, len);
fuxmlwr_isname(tagname);
fuxmlwr_tag(aTHX_ fuxmlwr_tail, ax, 1, items, 0, tagname, len);
INCLUDE_COMMAND: $^X -e '$FU::XMLWriter::XSPRINT=1; require "./FU/XMLWriter.pm"'
void DESTROY(fuxmlwr *wr)
CODE:
fuxmlwr_destroy(aTHX_ wr);