pg: Add json, jsonb, jsonpath support
NOW we're really getting to the part where this module is more awesome than DBD::Pg. (When I started working on this module I was expecting that the Postgres binary protocol would send jsonb in a binary format as well and that I'd be duplicating parts of the JSON parser/formatter to make that work, but it turns out that Postgres just uses plain json for exchange. Saves me some trouble, I guess)
This commit is contained in:
parent
7f1c48e0cf
commit
2aaec6a218
4 changed files with 98 additions and 49 deletions
78
c/jsonfmt.c
78
c/jsonfmt.c
|
|
@ -1,5 +1,5 @@
|
|||
typedef struct {
|
||||
fustr out;
|
||||
fustr *out;
|
||||
UV depth;
|
||||
int canon;
|
||||
int pretty; /* <0 when disabled, current nesting level otherwise */
|
||||
|
|
@ -9,7 +9,7 @@ static void fujson_fmt(pTHX_ fujson_fmt_ctx *, SV *);
|
|||
|
||||
static void fujson_fmt_indent(pTHX_ fujson_fmt_ctx *ctx) {
|
||||
if (ctx->pretty >= 0) {
|
||||
char *buf = fustr_write_buf(&ctx->out, 1 + ctx->pretty*3);
|
||||
char *buf = fustr_write_buf(ctx->out, 1 + ctx->pretty*3);
|
||||
*buf = '\n';
|
||||
memset(buf+1, ' ', ctx->pretty*3);
|
||||
}
|
||||
|
|
@ -30,8 +30,8 @@ static void fujson_fmt_str(pTHX_ fujson_fmt_ctx *ctx, const char *stri, size_t l
|
|||
croak("invalid codepoint encountered in string, cannot format to JSON");
|
||||
}
|
||||
|
||||
fustr_write_ch(&ctx->out, '\"');
|
||||
fustr_reserve(&ctx->out, len);
|
||||
fustr_write_ch(ctx->out, '\"');
|
||||
fustr_reserve(ctx->out, len);
|
||||
|
||||
while (off < len) {
|
||||
/* Fast path: no escaping needed */
|
||||
|
|
@ -51,25 +51,25 @@ static void fujson_fmt_str(pTHX_ fujson_fmt_ctx *ctx, const char *stri, size_t l
|
|||
off++;
|
||||
}
|
||||
}
|
||||
fustr_write(&ctx->out, (char *)str+loff, off-loff);
|
||||
fustr_write(ctx->out, (char *)str+loff, off-loff);
|
||||
|
||||
if (off < len) { /* early break, which means current byte needs special processing */
|
||||
switch (x) {
|
||||
case '"': fustr_write(&ctx->out, "\\\"", 2); break;
|
||||
case '\\': fustr_write(&ctx->out, "\\\\", 2); break;
|
||||
case 0x08: fustr_write(&ctx->out, "\\b", 2); break;
|
||||
case 0x09: fustr_write(&ctx->out, "\\t", 2); break;
|
||||
case 0x0a: fustr_write(&ctx->out, "\\n", 2); break;
|
||||
case 0x0c: fustr_write(&ctx->out, "\\f", 2); break;
|
||||
case 0x0d: fustr_write(&ctx->out, "\\r", 2); break;
|
||||
case '"': fustr_write(ctx->out, "\\\"", 2); break;
|
||||
case '\\': fustr_write(ctx->out, "\\\\", 2); break;
|
||||
case 0x08: fustr_write(ctx->out, "\\b", 2); break;
|
||||
case 0x09: fustr_write(ctx->out, "\\t", 2); break;
|
||||
case 0x0a: fustr_write(ctx->out, "\\n", 2); break;
|
||||
case 0x0c: fustr_write(ctx->out, "\\f", 2); break;
|
||||
case 0x0d: fustr_write(ctx->out, "\\r", 2); break;
|
||||
default:
|
||||
if (x < 0x80) {
|
||||
buf = (unsigned char *)fustr_write_buf(&ctx->out, 6);
|
||||
buf = (unsigned char *)fustr_write_buf(ctx->out, 6);
|
||||
memcpy(buf, "\\u00", 4);
|
||||
buf[4] = PL_hexdigit[(x >> 4) & 0x0f];
|
||||
buf[5] = PL_hexdigit[x & 0x0f];
|
||||
} else { /* x >= 0x80, !utf8, so encode as 2-byte UTF-8 */
|
||||
buf = (unsigned char *)fustr_write_buf(&ctx->out, 2);
|
||||
buf = (unsigned char *)fustr_write_buf(ctx->out, 2);
|
||||
buf[0] = 0xc0 | (x >> 6);
|
||||
buf[1] = 0x80 | (x & 0x3f);
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ static void fujson_fmt_str(pTHX_ fujson_fmt_ctx *ctx, const char *stri, size_t l
|
|||
}
|
||||
}
|
||||
|
||||
fustr_write_ch(&ctx->out, '\"');
|
||||
fustr_write_ch(ctx->out, '\"');
|
||||
}
|
||||
|
||||
static const char fujson_digits[] =
|
||||
|
|
@ -109,7 +109,7 @@ static void fujson_fmt_int(pTHX_ fujson_fmt_ctx *ctx, SV *val) {
|
|||
}
|
||||
|
||||
if (uv == 0) {
|
||||
fustr_write_ch(&ctx->out, '0');
|
||||
fustr_write_ch(ctx->out, '0');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -120,23 +120,23 @@ static void fujson_fmt_int(pTHX_ fujson_fmt_ctx *ctx, SV *val) {
|
|||
}
|
||||
if (uv > 0) *(--r) = '0' + (uv % 10);
|
||||
if (neg) *(--r) = '-';
|
||||
fustr_write(&ctx->out, r, 31 - (r - buf));
|
||||
fustr_write(ctx->out, r, 31 - (r - buf));
|
||||
}
|
||||
|
||||
static void fujson_fmt_av(pTHX_ fujson_fmt_ctx *ctx, AV *av) {
|
||||
int i, len = av_count(av);
|
||||
fustr_write_ch(&ctx->out, '[');
|
||||
fustr_write_ch(ctx->out, '[');
|
||||
ctx->pretty++;
|
||||
for (i=0; i<len; i++) {
|
||||
if (i) fustr_write_ch(&ctx->out, ',');
|
||||
if (i) fustr_write_ch(ctx->out, ',');
|
||||
fujson_fmt_indent(aTHX_ ctx);
|
||||
SV **sv = av_fetch(av, i, 0);
|
||||
if (sv) fujson_fmt(aTHX_ ctx, *sv); /* sv will have magic if av is tied, but fujson_fmt() handles that. */
|
||||
else fustr_write(&ctx->out, "null", 4);
|
||||
else fustr_write(ctx->out, "null", 4);
|
||||
}
|
||||
ctx->pretty--;
|
||||
if (i) fujson_fmt_indent(aTHX_ ctx);
|
||||
fustr_write_ch(&ctx->out, ']');
|
||||
fustr_write_ch(ctx->out, ']');
|
||||
}
|
||||
|
||||
static int fujson_fmt_hvcmp(const void *pa, const void *pb) {
|
||||
|
|
@ -159,12 +159,12 @@ static int fujson_fmt_hvcmp(const void *pa, const void *pb) {
|
|||
|
||||
static void fujson_fmt_hvkv(pTHX_ fujson_fmt_ctx *ctx, HV *hv, HE *he, char **hestr) {
|
||||
STRLEN helen;
|
||||
if (*hestr) fustr_write_ch(&ctx->out, ',');
|
||||
if (*hestr) fustr_write_ch(ctx->out, ',');
|
||||
fujson_fmt_indent(aTHX_ ctx);
|
||||
*hestr = HePV(he, helen);
|
||||
fujson_fmt_str(aTHX_ ctx, *hestr, helen, HeUTF8(he));
|
||||
if (ctx->pretty > 0) fustr_write(&ctx->out, " : ", 3);
|
||||
else fustr_write_ch(&ctx->out, ':');
|
||||
if (ctx->pretty > 0) fustr_write(ctx->out, " : ", 3);
|
||||
else fustr_write_ch(ctx->out, ':');
|
||||
fujson_fmt(aTHX_ ctx, UNLIKELY(SvMAGICAL(hv)) ? hv_iterval(hv, he) : HeVAL(he));
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ static void fujson_fmt_hv(pTHX_ fujson_fmt_ctx *ctx, HV *hv) {
|
|||
char *hestr = NULL;
|
||||
|
||||
int numkeys = hv_iterinit(hv);
|
||||
fustr_write_ch(&ctx->out, '{');
|
||||
fustr_write_ch(ctx->out, '{');
|
||||
ctx->pretty++;
|
||||
|
||||
/* Canonical order on tied hashes is not supported. Cpanel::JSON::XS has
|
||||
|
|
@ -204,7 +204,7 @@ static void fujson_fmt_hv(pTHX_ fujson_fmt_ctx *ctx, HV *hv) {
|
|||
}
|
||||
ctx->pretty--;
|
||||
if (hestr) fujson_fmt_indent(aTHX_ ctx);
|
||||
fustr_write_ch(&ctx->out, '}');
|
||||
fustr_write_ch(ctx->out, '}');
|
||||
}
|
||||
|
||||
static void fujson_fmt_obj(pTHX_ fujson_fmt_ctx *ctx, SV *rv, SV *obj) {
|
||||
|
|
@ -243,8 +243,8 @@ static void fujson_fmt(pTHX_ fujson_fmt_ctx *ctx, SV *val) {
|
|||
/* XXX: &PL_sv_yes and &PL_sv_no are proper booleans under 5.40, so no need
|
||||
* to explicitly check for those; does this work in 5.36 as well? */
|
||||
if (SvIsBOOL(val)) { /* Must check before IOKp & POKp, because bool implies both flags */
|
||||
if (BOOL_INTERNALS_sv_isbool_true(val)) fustr_write(&ctx->out, "true", 4);
|
||||
else fustr_write(&ctx->out, "false", 5);
|
||||
if (BOOL_INTERNALS_sv_isbool_true(val)) fustr_write(ctx->out, "true", 4);
|
||||
else fustr_write(ctx->out, "false", 5);
|
||||
} else if (SvPOKp(val)) {
|
||||
fujson_fmt_str(aTHX_ ctx, SvPVX(val), SvCUR(val), SvUTF8(val));
|
||||
} else if (SvNOKp(val)) { /* Must check before IOKp, because integer conversion might have been lossy */
|
||||
|
|
@ -252,9 +252,9 @@ static void fujson_fmt(pTHX_ fujson_fmt_ctx *ctx, SV *val) {
|
|||
if (isinfnan(nv)) croak("unable to format floating point NaN or Inf as JSON");
|
||||
/* XXX: Cpanel::JSON::XS appears to always append a ".0" for round numbers, other modules do not. */
|
||||
/* XXX#2: This doesn't support quadmath. Makefile.PL checks for that */
|
||||
fustr_reserve(&ctx->out, NV_DIG+1);
|
||||
Gconvert(nv, NV_DIG, 0, ctx->out.cur);
|
||||
ctx->out.cur += strlen(ctx->out.cur);
|
||||
fustr_reserve(ctx->out, NV_DIG+1);
|
||||
Gconvert(nv, NV_DIG, 0, ctx->out->cur);
|
||||
ctx->out->cur += strlen(ctx->out->cur);
|
||||
} else if (SvIOKp(val)) {
|
||||
fujson_fmt_int(aTHX_ ctx, val);
|
||||
} else if (SvROK(val)) {
|
||||
|
|
@ -270,7 +270,7 @@ static void fujson_fmt(pTHX_ fujson_fmt_ctx *ctx, SV *val) {
|
|||
else croak("unable to format reference '%s' as JSON", SvPV_nolen(val));
|
||||
ctx->depth++;
|
||||
} else if (!SvOK(val)) {
|
||||
fustr_write(&ctx->out, "null", 4);
|
||||
fustr_write(ctx->out, "null", 4);
|
||||
} else {
|
||||
croak("unable to format unknown value '%s' as JSON", SvPV_nolen(val));
|
||||
}
|
||||
|
|
@ -282,9 +282,11 @@ static SV *fujson_fmt_xs(pTHX_ I32 ax, I32 argc, SV *val) {
|
|||
int encutf8 = 0;
|
||||
char *arg;
|
||||
SV *r;
|
||||
fustr out;
|
||||
fujson_fmt_ctx ctx;
|
||||
|
||||
ctx.out.maxlen = 0;
|
||||
out.maxlen = 0;
|
||||
ctx.out = &out;
|
||||
ctx.depth = 0;
|
||||
ctx.pretty = INT_MIN;
|
||||
ctx.canon = 0;
|
||||
|
|
@ -298,17 +300,17 @@ static SV *fujson_fmt_xs(pTHX_ I32 ax, I32 argc, SV *val) {
|
|||
if (strcmp(arg, "canonical") == 0) ctx.canon = SvPVXtrue(r);
|
||||
else if (strcmp(arg, "pretty") == 0) ctx.pretty = SvPVXtrue(r) ? 0 : INT_MIN;
|
||||
else if (strcmp(arg, "utf8") == 0) encutf8 = SvPVXtrue(r);
|
||||
else if (strcmp(arg, "max_size") == 0) ctx.out.maxlen = SvUV(r);
|
||||
else if (strcmp(arg, "max_size") == 0) out.maxlen = SvUV(r);
|
||||
else if (strcmp(arg, "max_depth") == 0) ctx.depth = SvUV(r);
|
||||
else croak("Unknown flag: '%s'", arg);
|
||||
}
|
||||
if (ctx.out.maxlen == 0) ctx.out.maxlen = 1<<30;
|
||||
if (out.maxlen == 0) out.maxlen = 1<<30;
|
||||
if (ctx.depth == 0) ctx.depth = 512;
|
||||
|
||||
fustr_init(&ctx.out, sv_newmortal(), ctx.out.maxlen);
|
||||
fustr_init(&out, sv_newmortal(), out.maxlen);
|
||||
fujson_fmt(aTHX_ &ctx, val);
|
||||
if (ctx.pretty >= 0) fustr_write_ch(&ctx.out, '\n');
|
||||
r = fustr_done(&ctx.out);
|
||||
if (ctx.pretty >= 0) fustr_write_ch(&out, '\n');
|
||||
r = fustr_done(&out);
|
||||
if (!encutf8) SvUTF8_on(r);
|
||||
return r;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue