File Coverage

c/pgtypes.c
Criterion Covered Total %
statement 0 459 0.0
branch 0 422 0.0
condition n/a
subroutine n/a
pod n/a
total 0 881 0.0


line stmt bran cond sub pod time code
1             typedef struct fupg_tio fupg_tio;
2              
3             /* Send function, takes a Perl value and should write the binary encoded
4             * format into the given fustr. */
5             typedef void (*fupg_send_fn)(pTHX_ const fupg_tio *, SV *, fustr *);
6              
7             /* Receive function, takes a binary string and should return a Perl value. */
8             typedef SV *(*fupg_recv_fn)(pTHX_ const fupg_tio *, const char *, int);
9              
10             typedef struct {
11             char n[64];
12             } fupg_name;
13              
14             /* Record/composite type definition */
15             typedef struct {
16             int nattrs;
17             struct {
18             Oid oid;
19             fupg_name name;
20             } attrs[];
21             } fupg_record;
22              
23             /* Type I/O context */
24             struct fupg_tio {
25             Oid oid;
26             const char *name;
27             fupg_send_fn send;
28             fupg_recv_fn recv;
29             union {
30             fupg_tio *arrayelem;
31             struct {
32             const fupg_record *info;
33             fupg_tio *tio;
34             } record;
35             SV *cb;
36             };
37             };
38              
39             typedef struct {
40             Oid oid;
41             Oid elemoid; /* For arrays & domain types; relid for records */
42             fupg_name name;
43             fupg_send_fn send;
44             fupg_recv_fn recv;
45             } fupg_type;
46              
47              
48              
49             #define RECVFN(name) static SV *fupg_recv_##name(pTHX_ const fupg_tio *ctx __attribute__((unused)), const char *buf, int len)
50             #define SENDFN(name) static void fupg_send_##name(pTHX_ const fupg_tio *ctx __attribute__((unused)), SV *val, fustr *out)
51             #define RERR(msg, ...) fu_confess("Error parsing value for type '%s' (oid %u): "msg, ctx->name, ctx->oid __VA_OPT__(,) __VA_ARGS__)
52             #define SERR(msg, ...) fu_confess("Error converting Perl value '%s' to type '%s' (oid %u): "msg, SvPV_nolen(val), ctx->name, ctx->oid __VA_OPT__(,) __VA_ARGS__)
53             #define RLEN(l) if (l != len) RERR("expected %d bytes but got %d", l, len)
54              
55             /* Perl likes to play loose with SV-to-integer conversions, but that's not
56             * very fun when trying to store values in a database. Text-based bind
57             * parameters get stricter validation by Postgres, so let's emulate some of
58             * that for binary parameters as well. */
59             #define SIV(min, max) IV iv;\
60             if (SvIOK(val)) iv = SvIV(val); \
61             else if (SvNOK(val)) { \
62             NV nv = SvNV(val); \
63             if (nv < IV_MIN || nv > IV_MAX || fabs((double)(nv - floor(nv))) > 0.0000000001) SERR("expected integer");\
64             iv = SvIV(val); \
65             } else if (SvPOK(val)) {\
66             STRLEN sl; \
67             UV uv; \
68             char *s = SvPV(val, sl); \
69             if (*s == '-' && grok_atoUV(s+1, &uv, NULL) && uv <= ((UV)IV_MAX)+1) iv = SvIV(val);\
70             else if (grok_atoUV(s, &uv, NULL) && uv <= IV_MAX) iv = SvIV(val);\
71             else SERR("expected integer");\
72             } else SERR("expected integer");\
73             if (iv < min || iv > max) SERR("integer out of range")
74              
75             /* These are simply marker functions, not supposed to be called directly */
76 0           RECVFN(domain) { (void)buf; (void)len; RERR("domain type should not be handled by this function"); }
77 0           SENDFN(domain) { (void)out; SERR("domain type should not be handled by this function"); }
78              
79 0           RECVFN(bool) {
80 0 0         RLEN(1);
81 0 0         return *buf ? newSV_true() : newSV_false();
82             }
83              
84 0           SENDFN(bool) {
85 0           int r = fu_2bool(aTHX_ val);
86 0 0         if (r < 0) {
87             STRLEN l;
88 0           const char *x = SvPV(val, l);
89 0 0         if (l == 0 || (l == 1 && (*x == '0' || *x == 'f'))) r = 0;
    0          
    0          
    0          
90 0 0         else if (l == 1 && (*x == '1' || *x == 't')) r = 1;
    0          
    0          
91 0           else SERR("invalid boolean value: %s", x);
92             }
93 0           fustr_write_ch(out, r);
94 0           }
95              
96 0           RECVFN(void) {
97 0 0         RLEN(0);
98             (void)buf;
99 0           return newSV(0);
100             }
101              
102 0           SENDFN(void) {
103             (void)val; (void)out;
104 0           }
105              
106 0           RECVFN(int2) {
107 0 0         RLEN(2);
108 0           return newSViv(fu_frombeI(16, buf));
109             }
110              
111 0           SENDFN(int2) {
112 0 0         SIV(-32768, 32767);
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
113 0           fustr_writebeI(16, out, iv);
114 0           }
115              
116 0           RECVFN(int4) {
117 0 0         RLEN(4);
118 0           return newSViv(fu_frombeI(32, buf));
119             }
120              
121 0           SENDFN(int4) {
122 0 0         SIV(-2147483648, 2147483647);
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
123 0           fustr_writebeI(32, out, iv);
124 0           }
125              
126 0           RECVFN(int8) {
127 0 0         RLEN(8);
128 0           return newSViv(fu_frombeI(64, buf));
129             }
130              
131 0           SENDFN(int8) {
132 0 0         SIV(IV_MIN, IV_MAX);
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
133 0           fustr_writebeI(64, out, iv);
134 0           }
135              
136 0           RECVFN(uint4) {
137 0 0         RLEN(4);
138 0           return newSViv(fu_frombeU(32, buf));
139             }
140              
141 0           SENDFN(uint4) {
142 0 0         SIV(0, UINT32_MAX);
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
143 0           fustr_writebeU(32, out, iv);
144 0           }
145              
146 0           RECVFN(uint8) {
147 0 0         RLEN(8);
148 0           return newSVuv(fu_frombeU(64, buf));
149             }
150              
151 0           SENDFN(uint8) {
152             /* Doesn't have the nice input validation of 'SIV', but this type is pretty rare anyway */
153 0           fustr_writebeU(64, out, SvUV(val));
154 0           }
155              
156 0           RECVFN(bytea) {
157 0           return newSVpvn(buf, len);
158             }
159              
160 0           SENDFN(bytea) {
161             STRLEN len;
162 0           const char *buf = SvPVbyte(val, len);
163 0           fustr_write(out, buf, len);
164 0           }
165              
166 0           RECVFN(hex) {
167 0 0         SV *r = newSV(len ? len * 2 : 1);
168 0           SvPOK_only(r);
169 0           char *out = SvPVX(r);
170 0           const unsigned char *in = (const unsigned char *)buf;
171             int i;
172 0 0         for (i=0; i
173 0           *out++ = PL_hexdigit[(in[i] >> 4) & 0x0f];
174 0           *out++ = PL_hexdigit[in[i] & 0x0f];
175             }
176 0           *out = 0;
177 0           SvCUR_set(r, len * 2);
178 0           return r;
179             }
180              
181 0           SENDFN(hex) {
182             STRLEN len;
183 0           const char *in = SvPV(val, len);
184 0           const char *end = in + len;
185 0 0         if (len % 2) SERR("Invalid hex string");
186 0 0         while (in < end) {
187 0           int v = (fu_hexdig(*in)<<4) + fu_hexdig(in[1]);
188 0 0         if (v > 0xff) SERR("Invalid hex string");
189 0           fustr_write_ch(out, v);
190 0           in += 2;
191             }
192 0           }
193              
194 0           RECVFN(char) {
195 0 0         RLEN(1);
196 0           return newSVpvn(buf, len);
197             }
198              
199 0           SENDFN(char) {
200             STRLEN len;
201 0           const char *buf = SvPVbyte(val, len);
202 0 0         if (len != 1) SERR("expected 1-byte string");
203 0           fustr_write(out, buf, len);
204 0           }
205              
206             /* Works for many text-based column types, including receiving any value in the text format */
207 0           RECVFN(text) {
208 0 0         if (!is_c9strict_utf8_string((const U8*)buf, len)) RERR("invalid UTF-8");
209 0           return newSVpvn_utf8(buf, len, 1);
210             }
211              
212 0           SENDFN(text) {
213             STRLEN len;
214 0           const char *buf = SvPVutf8(val, len);
215 0           fustr_write(out, buf, len);
216 0           }
217              
218 0           RECVFN(float4) {
219 0 0         RLEN(4);
220 0           return newSVnv(fu_frombeT(float, 32, buf));
221             }
222              
223 0           SENDFN(float4) {
224 0 0         if (!looks_like_number(val)) SERR("expected a number");
225 0           fustr_writebeT(float, 32, out, SvNV(val));
226 0           }
227              
228 0           RECVFN(float8) {
229 0 0         RLEN(8);
230 0           return newSVnv(fu_frombeT(double, 64, buf));
231             }
232              
233 0           SENDFN(float8) {
234 0 0         if (!looks_like_number(val)) SERR("expected a number");
235 0           fustr_writebeT(double, 64, out, SvNV(val));
236 0           }
237              
238 0           RECVFN(json) {
239 0           fujson_parse_ctx json = {
240             .buf = (const unsigned char *)buf,
241 0           .end = (const unsigned char *)buf + len,
242             .depth = 512
243             };
244 0           SV *sv = fujson_parse(aTHX_ &json);
245 0 0         if (sv == NULL) RERR("invalid JSON");
246 0 0         if (json.buf != json.end) RERR("trailing garbage");
247 0           return sv;
248             }
249              
250 0           SENDFN(json) {
251 0           fujson_fmt_ctx json = { .out = out, .depth = 512, .canon = 1, .pretty = 0 };
252 0           fujson_fmt(aTHX_ &json, val);
253 0           }
254              
255 0           RECVFN(jsonb) {
256 0 0         if (len <= 1 || *buf != 1) RERR("invalid JSONB");
    0          
257 0           return fupg_recv_json(aTHX_ ctx, buf+1, len-1);
258             }
259              
260 0           SENDFN(jsonb) {
261 0           fustr_write_ch(out, 1);
262 0           fupg_send_json(aTHX_ ctx, val, out);
263 0           }
264              
265 0           RECVFN(jsonpath) {
266 0 0         if (len <= 1 || *buf != 1) RERR("invalid jsonpath");
    0          
267 0           return fupg_recv_text(aTHX_ ctx, buf+1, len-1);
268             }
269              
270 0           SENDFN(jsonpath) {
271 0           fustr_write_ch(out, 1);
272 0           fupg_send_text(aTHX_ ctx, val, out);
273 0           }
274              
275              
276             #define ARRAY_MAXDIM 100
277              
278 0           static SV *fupg_recv_array_elem(pTHX_ const fupg_tio *elem, const char *header, U32 dim, U32 ndim, const char **buf, const char *end) {
279             SV *r;
280 0 0         if (dim == ndim) {
281 0 0         if (end - *buf < 4) fu_confess("Invalid array format");
282 0           I32 len = fu_frombeI(32, *buf);
283 0           *buf += 4;
284              
285 0 0         if (end - *buf < len) fu_confess("Invalid array format");
286 0 0         if (len >= 0) {
287 0           r = elem->recv(aTHX_ elem, *buf, len);
288 0           *buf += len;
289             } else {
290 0           r = newSV(0);
291             }
292              
293             } else {
294 0           U32 n = fu_frombeU(32, header + dim*8);
295 0           AV *av = newAV_alloc_x(n);
296 0           r = sv_2mortal(newRV_noinc((SV *)av)); /* need to mortalize, we may croak */
297             U32 i;
298 0 0         for (i=0; i
299 0           av_push_simple(av, fupg_recv_array_elem(aTHX_ elem, header, dim+1, ndim, buf, end));
300 0           SvREFCNT_inc(r); /* We're safe now, make sure it survives the mortal stack cleanup */
301             }
302 0           return r;
303             }
304              
305 0           RECVFN(array) {
306 0 0         if (len < 12) RERR("input data too short");
307 0           U32 ndim = fu_frombeU(32, buf);
308             // buf+4 is hasnull, can safely ignore
309 0           Oid elemtype = fu_frombeU(32, buf+8);
310 0 0         if (elemtype != ctx->arrayelem->oid) RERR("invalid element type, expected %u but got %u", ctx->arrayelem->oid, elemtype);
311              
312 0 0         if (ndim == 0) return newRV_noinc((SV *)newAV());
313 0 0         if (ndim > ARRAY_MAXDIM) RERR("too many dimensions");
314 0 0         if ((U32)len < 12 + ndim*8) RERR("input data too short");
315              
316 0           const char *header = buf + 12;
317 0           const char *data = header + ndim * 8;
318 0           return fupg_recv_array_elem(aTHX_ ctx->arrayelem, header, 0, ndim, &data, buf+len);
319             }
320              
321 0           void fupg_send_array_elem(pTHX_ const fupg_tio *elem, const U32 *dims, U32 dim, U32 ndim, SV *v, fustr *out, int *hasnull) {
322 0           SvGETMAGIC(v);
323 0 0         if (dim == ndim) {
324 0 0         if (!SvOK(v)) {
325 0           fustr_write(out, "\xff\xff\xff\xff", 4);
326 0           *hasnull = 1;
327 0           return;
328             }
329 0 0         size_t lenoff = fustr_len(out);
330 0           fustr_write(out, "\0\0\0\0", 4);
331 0           elem->send(aTHX_ elem, v, out);
332 0 0         fu_tobeU(32, fustr_start(out) + lenoff, fustr_len(out) - lenoff - 4);
    0          
333 0           return;
334             }
335              
336 0 0         if (!SvROK(v)) fu_confess("Invalid array structure in bind parameter");
337 0           v = SvRV(v);
338 0           SvGETMAGIC(v);
339 0 0         if (SvTYPE(v) != SVt_PVAV) fu_confess("Invalid array structure in bind parameter");
340 0           AV *av = (AV*)v;
341 0 0         if (av_count(av) != dims[dim]) fu_confess("Invalid array structure in bind parameter");
342             U32 i;
343 0 0         for (i=0; i
344 0           SV **sv = av_fetch(av, i, 0);
345 0 0         if (!sv || !*sv) fu_confess("Invalid array structure in bind parameter");
    0          
346 0           fupg_send_array_elem(aTHX_ elem, dims, dim+1, ndim, *sv, out, hasnull);
347             }
348             }
349              
350 0           SENDFN(array) {
351 0           U32 ndim = 0;
352             U32 dims[ARRAY_MAXDIM];
353              
354             /* First figure out ndim and length-per-dim. The has-null flag and
355             * verification that each array-per-dimension has the same length is done
356             * while writing the elements.
357             * This is prone to errors if the elem type also accepts arrays as input,
358             * not quite sure how to deal with that case. */
359 0           SV *v = val;
360 0           while (true) {
361 0           SvGETMAGIC(v);
362 0 0         if (!SvROK(v)) break;
363 0           v = SvRV(v);
364 0           SvGETMAGIC(v);
365 0 0         if (SvTYPE(v) != SVt_PVAV) break;
366 0 0         if (ndim >= ARRAY_MAXDIM) SERR("too many dimensions");
367 0           dims[ndim] = av_count((AV*)v);
368 0 0         if (ndim > 0 && dims[ndim] == 0) SERR("nested arrays may not be empty");
    0          
369 0           ndim++;
370 0           SV **sv = av_fetch((AV*)v, 0, 0);
371 0 0         if (!sv || !*sv) break;
    0          
372 0           v = *sv;
373             }
374 0 0         if (ndim == 0) SERR("expected an array");
375 0 0         if (dims[0] == 0) ndim = 0;
376              
377             /* Write header */
378 0           fustr_writebeU(32, out, ndim);
379 0           fustr_write(out, "\0\0\0\0", 4); /* Placeholder for isnull */
380 0 0         size_t hasnull_off = fustr_len(out) - 1;
381 0           fustr_writebeU(32, out, ctx->arrayelem->oid);
382             U32 i;
383 0 0         for (i=0; i
384 0           fustr_writebeU(32, out, dims[i]);
385             /* int2vector and oidvector expect 0-based indexing,
386             * everything else defaults to 1-based indexing. */
387 0 0         if (ctx->oid == 22 || ctx->oid == 30) fustr_write(out, "\0\0\0\0", 4);
    0          
388 0           else fustr_write(out, "\0\0\0\1", 4);
389             }
390 0 0         if (ndim == 0) return;
391              
392             /* write the elements */
393 0           int hasnull = 0;
394 0           fupg_send_array_elem(aTHX_ ctx->arrayelem, dims, 0, ndim, val, out, &hasnull);
395 0 0         if (hasnull) fustr_start(out)[hasnull_off] = 1;
    0          
396             }
397              
398             #undef ARRAY_MAXDIM
399              
400              
401 0           RECVFN(record) {
402 0 0         if (len < 4) RERR("input data too short");
403 0           I32 nfields = fu_frombeI(32, buf);
404 0 0         if (nfields != ctx->record.info->nattrs) RERR("expected %d fields but got %d", ctx->record.info->nattrs, nfields);
405 0           buf += 4; len -= 4;
406 0           HV *hv = newHV();
407 0           SV *sv = sv_2mortal(newRV_noinc((SV *)hv));
408             I32 i;
409 0 0         for (i=0; i
410 0 0         if (len < 8) RERR("input data too short");
411 0           U32 oid = fu_frombeU(32, buf);
412 0 0         if (oid != ctx->record.info->attrs[i].oid)
413 0           RERR("expected field %d to be of type %u but got %u", i, ctx->record.info->attrs[i].oid, oid);
414 0           I32 vlen = fu_frombeI(32, buf+4);
415             SV *r;
416 0           buf += 8; len -= 8;
417 0 0         if (vlen > len) RERR("input data too short");
418 0 0         if (vlen >= 0) {
419 0           r = ctx->record.tio[i].recv(aTHX_ ctx->record.tio+i, buf, vlen);
420 0           buf += vlen; len -= vlen;
421             } else {
422 0           r = newSV(0);
423             }
424 0           hv_store(hv, ctx->record.info->attrs[i].name.n, -strlen(ctx->record.info->attrs[i].name.n), r, 0);
425             }
426 0           return SvREFCNT_inc(sv);
427             }
428              
429 0           SENDFN(record) {
430 0 0         if (!SvROK(val)) SERR("expected a hashref");
431 0           SV *sv = SvRV(val);
432 0           SvGETMAGIC(sv);
433 0 0         if (SvTYPE(sv) != SVt_PVHV) SERR("expected a hashref");
434 0           HV *hv = (HV *)sv;
435              
436 0           fustr_writebeU(32, out, ctx->record.info->nattrs);
437             I32 i;
438 0 0         for (i=0; irecord.info->nattrs; i++) {
439 0           fustr_writebeI(32, out, ctx->record.info->attrs[i].oid);
440 0           SV **rsv = hv_fetch(hv, ctx->record.info->attrs[i].name.n, -strlen(ctx->record.info->attrs[i].name.n), 0);
441 0 0         if (!rsv || !*rsv) {
    0          
442 0           fustr_writebeI(32, out, -1);
443 0           continue;
444             }
445 0           sv = *rsv;
446 0           SvGETMAGIC(sv);
447 0 0         if (!SvOK(sv)) {
448 0           fustr_writebeI(32, out, -1);
449 0           continue;
450             }
451 0 0         size_t lenoff = fustr_len(out);
452 0           fustr_write(out, "\0\0\0\0", 4);
453 0           ctx->record.tio[i].send(aTHX_ ctx->record.tio+i, sv, out);
454 0 0         fu_tobeU(32, fustr_start(out) + lenoff, fustr_len(out) - lenoff - 4);
    0          
455             }
456 0           }
457              
458              
459 0           RECVFN(perlcb) {
460 0           dSP;
461              
462 0           ENTER;
463 0           SAVETMPS;
464              
465 0 0         PUSHMARK(SP);
466 0 0         mXPUSHs(newSVpvn(buf, len));
467 0           PUTBACK;
468 0           call_sv(ctx->cb, G_SCALAR);
469 0           SPAGAIN;
470              
471 0           SV *ret = POPs;
472 0           SvREFCNT_inc(ret);
473 0           PUTBACK;
474              
475 0 0         FREETMPS;
476 0           LEAVE;
477 0           return ret;
478             }
479              
480 0           SENDFN(perlcb) {
481 0           dSP;
482              
483 0           ENTER;
484 0           SAVETMPS;
485              
486 0 0         PUSHMARK(SP);
487 0 0         XPUSHs(val);
488              
489 0           PUTBACK;
490 0           call_sv(ctx->cb, G_SCALAR);
491 0           SPAGAIN;
492              
493 0           SV *ret = POPs;
494 0           PUTBACK;
495              
496             STRLEN len;
497 0           const char *buf = SvPV(ret, len);
498 0           fustr_write(out, buf, len);
499              
500 0 0         FREETMPS;
501 0           LEAVE;
502 0           }
503              
504              
505 0           RECVFN(inet) { /* Also works for cidr */
506             char tmp[128];
507 0 0         if (len < 8) RERR("input data too short");
508             // 0: ip_family, 1: mask_bits, 2: is_cidr, 3: addrsize, 4: address
509              
510 0 0         if (buf[0] == 2) { /* INET */
511 0 0         RLEN(8);
512 0 0         if (!inet_ntop(AF_INET, buf+4, tmp, sizeof(tmp)-1)) RERR("%s", strerror(errno));
513 0 0         } else if (buf[0] == 3) { /* INET6 */
514 0 0         RLEN(20);
515 0 0         if (!inet_ntop(AF_INET6, buf+4, tmp, sizeof(tmp)-1)) RERR("%s", strerror(errno));
516 0           } else RERR("unknown address type");
517              
518 0 0         if (buf[2] || buf[1] != (buf[0] == 2 ? 32 : (char)128))
    0          
    0          
519 0           return newSVpvf("%s/%d", tmp, (unsigned char)buf[1]);
520 0           return newSVpv(tmp, 0);
521             }
522              
523 0           SENDFN(inet) {
524             char tmp[128];
525             STRLEN len;
526 0           const char *in = SvPV(val, len);
527 0 0         if (len >= sizeof(tmp)) SERR("input too long");
528 0 0         char family = strchr(in, ':') ? 3 : 2;
529 0 0         char *wr = fustr_write_buf(out, family == 2 ? 8 : 20);
530 0           unsigned char *mask = (unsigned char*)wr+1;
531 0           wr[0] = family;
532 0 0         *mask = family == 2 ? 32 : 128;
533 0           wr[2] = ctx->oid == 650;
534 0 0         wr[3] = family == 2 ? 4 : 16;
535              
536 0           char *slash = strchr(in, '/');
537 0 0         if (slash && slash - in < 100) {
    0          
538 0           memcpy(tmp, in, slash - in);
539 0           tmp[slash - in] = 0;
540 0           in = tmp;
541             }
542 0 0         if (inet_pton(family == 2 ? AF_INET : AF_INET6, in, wr+4) != 1)
    0          
543 0           SERR("invalid address");
544              
545 0 0         if (slash) {
546 0           UV uv = 129;
547 0 0         if (!grok_atoUV(slash+1, &uv, NULL) || uv > *mask)
    0          
548 0           SERR("invalid mask");
549 0           *mask = uv;
550             }
551 0           }
552              
553 0           RECVFN(uuid) {
554 0 0         RLEN(16);
555             char tmp[64];
556 0           char *out = tmp;
557 0           const unsigned char *in = (const unsigned char *)buf;
558             int i;
559 0 0         for (i=0; i<16; i++) {
560 0 0         if (i == 4 || i == 6 || i == 8 || i == 10) *out++ = '-';
    0          
    0          
    0          
561 0           *out++ = PL_hexdigit[(in[i] >> 4) & 0x0f];
562 0           *out++ = PL_hexdigit[in[i] & 0x0f];
563             }
564 0           *out = '\0';
565 0           return newSVpv(tmp, 0);
566             }
567              
568 0           SENDFN(uuid) {
569 0           const char *in = SvPV_nolen(val);
570 0           int bytes = 0;
571 0           unsigned char dig = 0x10;
572 0 0         if (*in == '{') in++;
573 0 0         for (; *in; in++) {
574 0 0         if (*in == '}') break;
575 0 0         if (dig == 0x10 && *in == '-') continue;
    0          
576 0           int x = fu_hexdig(*in);
577 0 0         if (x > 0x10) SERR("invalid UUID");
578 0 0         if (bytes >= 16) SERR("invalid UUID");
579 0 0         if (dig == 0x10) dig = x;
580             else {
581 0           fustr_write_ch(out, (dig << 4) + x);
582 0           bytes++;
583 0           dig = 0x10;
584             }
585             }
586 0 0         if (dig != 0x10 || bytes != 16) SERR("invalid UUID");
    0          
587 0           }
588              
589             /* Postgres uses 2000-01-01 as epoch, we stick with POSIX 1970-01-01 */
590             #define UNIX_PG_EPOCH (10957*86400)
591              
592 0           RECVFN(timestamp) {
593 0 0         RLEN(8);
594 0           return newSVnv(((NV)fu_frombeI(64, buf) / 1000000) + UNIX_PG_EPOCH);
595             }
596              
597 0           SENDFN(timestamp) {
598 0 0         if (!looks_like_number(val)) SERR("expected a number");
599 0           I64 ts = (SvNV(val) - UNIX_PG_EPOCH) * 1000000;
600 0           fustr_writebeI(64, out, ts);
601 0           }
602              
603 0           RECVFN(date) {
604 0 0         RLEN(4);
605 0           return newSVuv(((UV)fu_frombeI(32, buf)) * 86400 + UNIX_PG_EPOCH);
606             }
607              
608 0           SENDFN(date) {
609 0 0         if (!looks_like_number(val)) SERR("expected a number");
610 0           fustr_writebeI(32, out, (SvIV(val) - UNIX_PG_EPOCH) / 86400);
611 0           }
612              
613 0           RECVFN(date_str) {
614 0 0         RLEN(4);
615 0           time_t ts = ((time_t)fu_frombeI(32, buf)) * 86400 + UNIX_PG_EPOCH;
616             struct tm tm;
617 0           gmtime_r(&ts, &tm);
618 0           return newSVpvf("%04d-%02d-%02d", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday);
619             }
620              
621 0           SENDFN(date_str) {
622             int year, month, day;
623 0 0         if (sscanf(SvPV_nolen(val), "%4d-%2d-%2d", &year, &month, &day) != 3) SERR("invalid date format");
624             /* Can't use mktime() hackery here because libc has no UTC variant. Code
625             * below is adapted from PostgreSQL date2j() instead. */
626 0 0         if (month > 2) {
627 0           month += 1;
628 0           year += 4800;
629             } else {
630 0           month += 13;
631 0           year += 4799;
632             }
633 0           int century = year / 100;
634 0           int v = year * 365 - 32167;
635 0           v += year / 4 - century + century / 4;
636 0           v += 7834 * month / 256 + day;
637 0           v -= 2451545; /* Julian -> Postgres */
638 0           fustr_writebeI(32, out, v);
639 0           }
640              
641             #undef UNIX_PG_EPOCH
642              
643 0           RECVFN(time) {
644 0 0         RLEN(8);
645 0           return newSVnv(((NV)fu_frombeI(64, buf)) / 1000000);
646             }
647              
648 0           SENDFN(time) {
649 0 0         if (!looks_like_number(val)) SERR("expected a number");
650 0           fustr_writebeI(64, out, SvNV(val) * 1000000);
651 0           }
652              
653              
654              
655             /* VNDB types */
656              
657             const char vndbtag_alpha[] = "\0""abcdefghijklmnopqrstuvwxyz?????";
658              
659 0           static I16 vndbtag_parse(char **str) {
660 0           I16 tag = 0;
661 0 0         if (**str >= 'a' && **str <= 'z') {
    0          
662 0           tag = (**str - 'a' + 1) << 10;
663 0           (*str)++;
664 0 0         if (**str >= 'a' && **str <= 'z') {
    0          
665 0           tag |= (**str - 'a' + 1) << 5;
666 0           (*str)++;
667 0 0         if (**str >= 'a' && **str <= 'z') {
    0          
668 0           tag |= **str - 'a' + 1;
669 0           (*str)++;
670             }
671             }
672             }
673 0           return tag;
674             }
675              
676 0           void vndbtag_fmt(I16 tag, char *out) {
677 0           out[0] = vndbtag_alpha[(tag >> 10) & 31];
678 0           out[1] = vndbtag_alpha[(tag >> 5) & 31];
679 0           out[2] = vndbtag_alpha[(tag >> 0) & 31];
680 0           out[3] = 0;
681 0           }
682              
683 0           RECVFN(vndbtag) {
684 0 0         RLEN(2);
685 0           SV *r = newSV(4);
686 0           SvPOK_only(r);
687 0           vndbtag_fmt(fu_frombeI(16, buf), SvPVX(r));
688 0           SvCUR_set(r, strlen(SvPVX(r)));
689 0           return r;
690             }
691              
692 0           SENDFN(vndbtag) {
693 0           char *t = SvPV_nolen(val);
694 0           I16 v = vndbtag_parse(&t);
695 0 0         if (*t) SERR("Invalid vndbtag: '%s'", SvPV_nolen(val));
696 0           fustr_writebeI(16, out, v);
697 0           }
698              
699              
700             #define VNDBID2_MAXNUM (((I64)1<<48)-1)
701              
702 0           RECVFN(vndbid) {
703 0 0         RLEN(8);
704 0           I64 v = fu_frombeI(64, buf);
705             char tbuf[4];
706 0           vndbtag_fmt(v >> 48, tbuf);
707 0           return newSVpvf("%s%"UVuf, tbuf, (UV)(v & VNDBID2_MAXNUM));
708             }
709              
710 0           SENDFN(vndbid) {
711 0           char *ostr = SvPV_nolen(val), *str = ostr;
712             UV num;
713 0           I16 tag = vndbtag_parse(&str);
714 0 0         if (!grok_atoUV(str, &num, NULL) || num > VNDBID2_MAXNUM) SERR("invalid vndbid '%s'", ostr);
    0          
715 0           fustr_writebeI(64, out, ((I64)tag)<<48 | num);
716 0           }
717              
718              
719             #undef SIV
720             #undef RLEN
721             #undef RECVFN
722             #undef SENDFN
723              
724              
725              
726              
727             /* List of types we handle directly in this module.
728             Ideally, this includes everything returned by:
729              
730             SELECT oid, typname, typelem, typreceive
731             FROM pg_type t
732             WHERE typtype = 'b'
733             AND typnamespace = 'pg_catalog'::regnamespace
734             AND (typelem = 0 OR EXISTS(SELECT 1 FROM pg_type e WHERE e.oid = t.typelem AND e.typtype = 'b'))
735             ORDER by oid
736              
737             (i.e. all base types and arrays of base types)
738             Plus hopefully a bunch of common extension types.
739              
740             The "reg#" types are a bit funny: the Postgres devs obviously realized that
741             writing JOINs is cumbersome, so they hacked together a numeric identifier
742             type that automatically resolves to a string when formatted as text, or
743             performs a lookup in the database when parsing text. In the text format, you
744             don't get to see the numeric identifier, but sadly that conversion is not
745             performed in the byte format so we're dealing with numbers instead. Oh well.
746             Not worth writing custom lookup code for, users will have to adapt.
747              
748             Ordered by oid to support binary search.
749             (name is only used when formatting error messages, for now) */
750             #define BUILTINS \
751             B( 16, "bool", bool )\
752             B( 17, "bytea", bytea )\
753             B( 18, "char", char )\
754             B( 19, "name", text )\
755             B( 20, "int8", int8 )\
756             B( 21, "int2", int2 )\
757             A( 22, "int2vector", 21 )\
758             B( 23, "int4", int4 )\
759             B( 24, "regproc", uint4 )\
760             B( 25, "text", text )\
761             B( 26, "oid", uint4 )\
762             /* 27 tid: u32 block, u16 offset; represent as hash? */ \
763             B( 28, "xid", uint4 )\
764             B( 29, "cid", uint4 )\
765             A( 30, "oidvector", 26 )\
766             B( 114, "json", json )\
767             B( 142, "xml", text )\
768             A( 143, "_xml", 142 )\
769             B( 194, "pg_node_tree", text ) /* can't be used as a bind param */\
770             A( 199, "_json", 114 )\
771             A( 271, "_xid8", 5069 )\
772             /* 600 point */\
773             /* 601 lseg */\
774             /* 602 path */\
775             /* 603 box */\
776             /* 604 polygon */\
777             /* 628 line */\
778             A( 629, "_line", 628 )\
779             B( 650, "cidr", inet )\
780             A( 651, "_cidr", 650 )\
781             B( 700, "float4", float4)\
782             B( 701, "float8", float8)\
783             /* 718 circle */\
784             A( 719, "_circle", 718 )\
785             /* 774 macaddr8 */\
786             A( 775, "_macaddr8", 774 )\
787             /* 790 money */\
788             A( 791, "_money", 790 )\
789             /* 829 macaddr */\
790             B( 869, "inet", inet )\
791             A( 1000, "_bool", 16 )\
792             A( 1001, "_bytea", 17 )\
793             A( 1002, "_char", 18 )\
794             A( 1003, "_name", 19 )\
795             A( 1005, "_int2", 21 )\
796             A( 1006, "_int2vector", 22 )\
797             A( 1007, "_int4", 23 )\
798             A( 1008, "_regproc", 24 )\
799             A( 1009, "_text", 25 )\
800             A( 1010, "_tid", 27 )\
801             A( 1011, "_xid", 28 )\
802             A( 1012, "_cid", 29 )\
803             A( 1013, "_oidvector", 30 )\
804             A( 1014, "_bpchar", 1042 )\
805             A( 1015, "_varchar", 1043 )\
806             A( 1016, "_int8", 20 )\
807             A( 1017, "_point", 600 )\
808             A( 1018, "_lseg", 601 )\
809             A( 1019, "_path", 602 )\
810             A( 1020, "_box", 603 )\
811             A( 1021, "_float4", 700 )\
812             A( 1022, "_float8", 701 )\
813             A( 1027, "_polygon", 604 )\
814             A( 1028, "_oid", 26 )\
815             /* 1033 aclitem, does not support send/recv */\
816             /* A( 1034, "_aclitem", 1033 ) */\
817             A( 1040, "_macaddr", 829 )\
818             A( 1041, "_inet", 869 )\
819             B( 1042, "bpchar", text )\
820             B( 1043, "varchar", text )\
821             B( 1082, "date", date )\
822             B( 1083, "time", time )\
823             B( 1114, "timestamp", timestamp)\
824             A( 1115, "_timestamp", 1114 )\
825             A( 1182, "_date", 1082 )\
826             A( 1183, "_time", 1083 )\
827             B( 1184, "timestamptz", timestamp)\
828             A( 1185, "_timestamptz", 1184 )\
829             /* 1186 interval */\
830             A( 1187, "_interval", 1186 )\
831             A( 1231, "_numeric", 1700 )\
832             /* 1266 timetz */\
833             A( 1270, "_timetz", 1266 )\
834             /* 1560 bit */\
835             A( 1561, "_bit", 1560 )\
836             /* 1562 varbit */\
837             A( 1563, "_varbit", 1562 )\
838             /* 1700 numeric */\
839             B( 1790, "refcursor", text )\
840             A( 2201, "_refcursor", 1790 )\
841             B( 2202, "regprocedure", uint4 )\
842             B( 2203, "regoper", uint4 )\
843             B( 2204, "regoperator", uint4 )\
844             B( 2205, "regclass", uint4 )\
845             B( 2206, "regtype", uint4 )\
846             A( 2207, "_regprocedure", 2202 )\
847             A( 2208, "_regoper", 2203 )\
848             A( 2209, "_regoperator", 2204 )\
849             A( 2210, "_regclass", 2205 )\
850             A( 2211, "_regtype", 2206 )\
851             B( 2278, "void", void )\
852             A( 2949, "_txid_snapshot", 2970 )\
853             B( 2950, "uuid", uuid )\
854             A( 2951, "_uuid", 2950 )\
855             /* 2970 txid_snapshot: same as pg_snapshot */\
856             /* 3220 pg_lsn: uint64 with custom formatting */\
857             A( 3221, "_pg_lsn", 3220 )\
858             /* 3361 pg_ndistinct */\
859             /* 3402 pg_dependencies */\
860             /* 3614 tsvector */\
861             /* 3615 tsquery */\
862             /* 3642 gtsvector, does not support send/recv */\
863             A( 3643, "_tsvector", 3614 )\
864             /*A( 3644, "_gtsvector", 3642 )*/\
865             A( 3645, "_tsquery", 3615 )\
866             B( 3734, "regconfig", uint4 )\
867             A( 3735, "_regconfig", 3734 )\
868             B( 3769, "regdictionary", uint4 )\
869             A( 3770, "_regdictionary", 3769 )\
870             B( 3802, "jsonb", jsonb )\
871             A( 3807, "_jsonb", 3802 )\
872             B( 4072, "jsonpath", jsonpath)\
873             A( 4073, "_jsonpath", 4072 )\
874             B( 4089, "regnamespace", uint4 )\
875             A( 4090, "_regnamespace", 4089 )\
876             B( 4096, "regrole", uint4 )\
877             A( 4097, "_regrole", 4096 )\
878             B( 4191, "regcollation", uint4 )\
879             A( 4192, "_regcollation", 4191 )\
880             /* 4600 pg_brin_bloom_summary */\
881             /* 4601 pg_brin_minmax_multi_summary */\
882             /* 5017 pg_mcv_list */\
883             /* 5038 pg_snapshot: int4 nxip, int8 xmin, int8 xmax, int8 xip */\
884             A( 5039, "_pg_snapshot", 5038 )\
885             B( 5069, "xid8", uint8 )
886              
887             static const fupg_type fupg_builtin[] = {
888             #define B(oid, name, fun) { oid, 0, {name"\0"}, fupg_send_##fun, fupg_recv_##fun },
889             #define A(oid, name, eoid) { oid, eoid, {name"\0"}, fupg_send_array, fupg_recv_array },
890             BUILTINS
891             #undef B
892             #undef A
893             };
894              
895             #undef BUILTINS
896             #define FUPG_BUILTIN (sizeof(fupg_builtin) / sizeof(fupg_type))
897              
898              
899             /* List of types identified by name */
900              
901             #define DYNOID\
902             T("vndbtag", vndbtag)\
903             T("vndbid", vndbid)
904              
905             static const fupg_type fupg_dynoid[] = {
906             #define T(name, fun) { 0, 0, {name"\0"}, fupg_send_##fun, fupg_recv_##fun },
907             DYNOID
908             #undef T
909             };
910              
911             #undef DYNOID
912             #define FUPG_DYNOID (sizeof(fupg_dynoid) / sizeof(fupg_type))
913              
914              
915             /* List of special types for use with set_type() */
916              
917             #define SPECIALS\
918             T("$date_str", date_str)\
919             T("$hex", hex )
920              
921             static const fupg_type fupg_specials[] = {
922             #define T(name, fun) { 0, 0, {name"\0"}, fupg_send_##fun, fupg_recv_##fun },
923             SPECIALS
924             #undef T
925             };
926              
927             #undef SPECIALS
928             #define FUPG_SPECIALS (sizeof(fupg_specials) / sizeof(fupg_type))
929              
930              
931             static const fupg_type fupg_type_perlcb = { 0, 0, {"$perl_cb"}, fupg_send_perlcb, fupg_recv_perlcb };
932              
933              
934 0           static const fupg_type *fupg_type_byoid(const fupg_type *list, int len, Oid oid) {
935 0           int i, b = 0, e = len-1;
936 0 0         while (b <= e) {
937 0           i = b + (e - b)/2;
938 0 0         if (list[i].oid == oid) return list+i;
939 0 0         if (list[i].oid < oid) b = i+1;
940 0           else e = i-1;
941             }
942 0           return NULL;
943             }
944              
945 0           static const fupg_type *fupg_builtin_byoid(Oid oid) {
946 0           return fupg_type_byoid(fupg_builtin, FUPG_BUILTIN, oid);
947             }
948              
949 0           static const fupg_type *fupg_dynoid_byname(const char *name) {
950             size_t i;
951 0 0         for (i=0; i
952 0 0         if (strcmp(fupg_dynoid[i].name.n, name) == 0)
953 0           return fupg_dynoid+i;
954 0           return NULL;
955             }
956              
957 0           static const fupg_type *fupg_builtin_byname(const char *name) {
958             size_t i;
959 0           const fupg_type *r = fupg_dynoid_byname(name);
960 0 0         if (r) return r;
961              
962             /* XXX: Can use binary search here if the list of specials grows.
963             * That list does not have to be ordered by oid. */
964 0 0         for (i=0; i
965 0 0         if (strcmp(fupg_specials[i].name.n, name) == 0)
966 0           return fupg_specials+i;
967              
968 0 0         for (i=0; i
969 0 0         if (strcmp(fupg_builtin[i].name.n, name) == 0)
970 0           return fupg_builtin+i;
971 0           return NULL;
972             }