Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : #include "monetdb_config.h"
14 :
15 : #include "copybinary.h"
16 : #include "copybinary_support.h"
17 : #include "sql_bincopyconvert.h"
18 : #include "sql.h"
19 : #include "gdk.h"
20 : #include "mal_backend.h"
21 : #include "mal_interpreter.h"
22 : #include "mstring.h"
23 :
24 :
25 : #define bailout(...) do { \
26 : msg = createException(MAL, mal_operator, SQLSTATE(42000) __VA_ARGS__); \
27 : goto end; \
28 : } while (0)
29 :
30 :
31 : static str
32 4 : validate_bit(void *dst_, size_t count, int width, const char *filename)
33 : {
34 4 : (void)width;
35 4 : const unsigned char *data = dst_;
36 :
37 10 : for (size_t i = 0; i < count; i++) {
38 6 : if (data[i] > 1 && data[i] != 0x80)
39 0 : throw(SQL, "convert_bit", SQLSTATE(22003) "invalid boolean byte value %d in %s", data[i], filename);
40 : }
41 : return MAL_SUCCEED;
42 : }
43 :
44 : // width is only nonzero for DECIMAL types. For plain integer types it is 0.
45 : #define VALIDATE_DECIMAL(TYP,NIL_VALUE) do { \
46 : if (width) { \
47 : TYP m = 1; \
48 : for (int i = 0; i < width; i++) \
49 : m *= 10; \
50 : TYP *dst = dst_; \
51 : for (size_t i = 0; i < count; i++) { \
52 : if (dst[i] == NIL_VALUE) \
53 : continue; \
54 : if (dst[i] >= m || dst[i] <= -m) \
55 : throw(SQL, "convert", SQLSTATE(22003) "decimal out of range in %s", filename); \
56 : } \
57 : } \
58 : } while (0)
59 :
60 :
61 : static str
62 9 : byteswap_sht(void *dst_, void *src_, size_t count, bool byteswap)
63 : {
64 9 : assert(byteswap); (void)byteswap; // otherwise, why call us?
65 : sht *dst = dst_;
66 : const sht *src = src_;
67 4000015 : for (size_t i = 0; i < count; i++)
68 4000006 : *dst++ = copy_binary_byteswap16(*src++);
69 9 : return MAL_SUCCEED;
70 : }
71 :
72 : static str
73 42 : validate_sht(void *dst_, size_t count, int width, const char *filename)
74 : {
75 3000087 : VALIDATE_DECIMAL(sht, sht_nil);
76 : return MAL_SUCCEED;
77 : }
78 :
79 : static str
80 41 : byteswap_int(void *dst_, void *src_, size_t count, bool byteswap)
81 : {
82 41 : assert(byteswap); (void)byteswap; // otherwise, why call us?
83 : int *dst = dst_;
84 : const int *src = src_;
85 8000071 : for (size_t i = 0; i < count; i++)
86 8000030 : *dst++ = copy_binary_byteswap32(*src++);
87 41 : return MAL_SUCCEED;
88 : }
89 :
90 : static str
91 284 : validate_int(void *dst_, size_t count, int width, const char *filename)
92 : {
93 4000447 : VALIDATE_DECIMAL(int, int_nil);
94 : return MAL_SUCCEED;
95 : }
96 :
97 : static str
98 33 : byteswap_lng(void *dst_, void *src_, size_t count, bool byteswap)
99 : {
100 33 : assert(byteswap); (void)byteswap; // otherwise, why call us?
101 : lng *dst = dst_;
102 : const lng *src = src_;
103 4000039 : for (size_t i = 0; i < count; i++)
104 4000006 : *dst++ = copy_binary_byteswap64(*src++);
105 33 : return MAL_SUCCEED;
106 : }
107 :
108 : static str
109 103 : validate_lng(void *dst_, size_t count, int width, const char *filename)
110 : {
111 4000433 : VALIDATE_DECIMAL(lng, lng_nil);
112 : return MAL_SUCCEED;
113 : }
114 :
115 : #ifdef HAVE_HGE
116 : static str
117 1 : byteswap_hge(void *dst_, void *src_, size_t count, bool byteswap)
118 : {
119 1 : assert(byteswap); (void)byteswap; // otherwise, why call us?
120 : hge *dst = dst_;
121 : const hge *src = src_;
122 7 : for (size_t i = 0; i < count; i++)
123 6 : *dst++ = copy_binary_byteswap128(*src++);
124 1 : return MAL_SUCCEED;
125 : }
126 :
127 : static str
128 94 : validate_hge(void *dst_, size_t count, int width, const char *filename)
129 : {
130 4001215 : VALIDATE_DECIMAL(hge, hge_nil);
131 : return MAL_SUCCEED;
132 : }
133 : #endif
134 :
135 : static str
136 17 : byteswap_flt(void *dst_, void *src_, size_t count, bool byteswap)
137 : {
138 : // Verify that size and alignment requirements of flt do not exceed int.
139 : // This is important because we use the int32 byteswap to byteswap the floats.
140 17 : assert(sizeof(uint32_t) == sizeof(flt));
141 17 : assert(sizeof(struct { char dummy; uint32_t ui; }) >= sizeof(struct { char dummy; flt f; }));
142 :
143 17 : assert(byteswap); (void)byteswap; // otherwise, why call us?
144 : int *dst = dst_;
145 : const int *src = src_;
146 4000020 : for (size_t i = 0; i < count; i++)
147 4000003 : *dst++ = copy_binary_byteswap32(*src++);
148 17 : return MAL_SUCCEED;
149 : }
150 :
151 : static str
152 33 : byteswap_dbl(void *dst_, void *src_, size_t count, bool byteswap)
153 : {
154 : // Verify that size and alignment requirements of dbl do not exceed lng
155 : // This is important because we use the int64 byteswap to byteswap the doubles.
156 33 : assert(sizeof(uint64_t) == sizeof(dbl));
157 33 : assert(sizeof(struct { char dummy; uint64_t ui; }) >= sizeof(struct { char dummy; dbl f; }));
158 :
159 33 : assert(byteswap); (void)byteswap; // otherwise, why call us?
160 : lng *dst = dst_;
161 : const lng *src = src_;
162 4000036 : for (size_t i = 0; i < count; i++)
163 4000003 : *dst++ = copy_binary_byteswap64(*src++);
164 33 : return MAL_SUCCEED;
165 : }
166 :
167 :
168 : static str
169 10 : decode_date(void *dst_, void *src_, size_t count, bool byteswap)
170 : {
171 10 : date *dst = dst_;
172 10 : copy_binary_date *src = src_;
173 :
174 2000014 : for (size_t i = 0; i < count; i++) {
175 2000004 : copy_binary_date incoming;
176 2000004 : if (!byteswap)
177 2000002 : incoming = *src++;
178 : else
179 2 : incoming = copy_binary_byteswap_date(*src++);
180 2000004 : date value = date_create(incoming.year, incoming.month, incoming.day);
181 2000004 : *dst++ = value;
182 : }
183 10 : return MAL_SUCCEED;
184 : }
185 :
186 : static str
187 10 : encode_date(void *dst_, void *src_, size_t count, bool byteswap)
188 : {
189 10 : copy_binary_date *dst = dst_;
190 10 : date *src = src_;
191 2000526 : for (size_t i = 0; i < count; i++) {
192 2000516 : date dt = *src++;
193 2000516 : if (is_date_nil(dt)) {
194 199526 : *dst++ = (copy_binary_date){
195 : .day = 0xFF,
196 : .month = 0xFF,
197 : .year = -1,
198 : };
199 199526 : continue;
200 : }
201 1800990 : int16_t year = date_year(dt);
202 1800990 : if (byteswap)
203 0 : year = copy_binary_byteswap16(year);
204 1800990 : *dst++ = (copy_binary_date){
205 1800990 : .day = date_day(dt),
206 1800990 : .month = date_month(dt),
207 : .year = year,
208 : };
209 : }
210 10 : return MAL_SUCCEED;
211 : }
212 :
213 : static str
214 16 : decode_time(void *dst_, void *src_, size_t count, bool byteswap)
215 : {
216 16 : daytime *dst = dst_;
217 16 : copy_binary_time *src = src_;
218 :
219 2000016 : for (size_t i = 0; i < count; i++) {
220 2000000 : copy_binary_time incoming;
221 2000000 : if (!byteswap)
222 2000000 : incoming = *src++;
223 : else
224 0 : incoming = copy_binary_byteswap_time(*src++);
225 2000000 : daytime value = daytime_create(incoming.hours, incoming.minutes, incoming.seconds, incoming.ms);
226 2000000 : *dst++ = value;
227 : }
228 16 : return MAL_SUCCEED;
229 : }
230 :
231 : static str
232 16 : encode_time(void *dst_, void *src_, size_t count, bool byteswap)
233 : {
234 16 : copy_binary_time *dst = dst_;
235 16 : daytime *src = src_;
236 2000016 : for (size_t i = 0; i < count; i++) {
237 2000000 : daytime tm = *src++;
238 2000000 : if (is_daytime_nil(tm)) {
239 199402 : *dst++ = (copy_binary_time){
240 : .ms = 0xFFFFFFFF,
241 : .seconds = 0xFF,
242 : .minutes = 0xFF,
243 : .hours = 0xFF,
244 : .padding = 0xFF,
245 : };
246 199402 : continue;
247 : }
248 1800598 : uint32_t ms = daytime_usec(tm);
249 1800598 : if (byteswap)
250 0 : ms = copy_binary_byteswap32(ms);
251 1800598 : *dst++ = (copy_binary_time){
252 : .ms = ms,
253 1800598 : .seconds = daytime_sec(tm),
254 1800598 : .minutes = daytime_min(tm),
255 1800598 : .hours = daytime_hour(tm),
256 : };
257 : }
258 16 : return MAL_SUCCEED;
259 : }
260 :
261 : static str
262 37 : decode_timestamp(void *dst_, void *src_, size_t count, bool byteswap)
263 : {
264 37 : timestamp *dst = dst_;
265 37 : copy_binary_timestamp *src = src_;
266 :
267 2000041 : for (size_t i = 0; i < count; i++) {
268 2000004 : copy_binary_timestamp incoming;
269 2000004 : if (!byteswap)
270 2000002 : incoming = *src++;
271 : else
272 2 : incoming = copy_binary_byteswap_timestamp(*src++);
273 2000004 : date dt = date_create(incoming.date.year, incoming.date.month, incoming.date.day);
274 2000004 : daytime tm = daytime_create(incoming.time.hours, incoming.time.minutes, incoming.time.seconds, incoming.time.ms);
275 2000004 : timestamp value = timestamp_create(dt, tm);
276 2000004 : *dst++ = value;
277 : }
278 37 : return MAL_SUCCEED;
279 : }
280 :
281 :
282 : static str
283 24 : encode_timestamp(void *dst_, void *src_, size_t count, bool byteswap)
284 : {
285 24 : copy_binary_timestamp *dst = dst_;
286 24 : timestamp *src = src_;
287 2000024 : for (size_t i = 0; i < count; i++) {
288 2000000 : timestamp value = *src++;
289 2000000 : if (is_timestamp_nil(value)) {
290 199402 : *dst++ = (copy_binary_timestamp) {
291 : .time = {
292 : .ms = 0xFFFFFFFF,
293 : .seconds = 0xFF,
294 : .minutes = 0xFF,
295 : .hours = 0xFF,
296 : .padding = 0xFF,
297 : },
298 : .date = {
299 : .day = 0xFF,
300 : .month = 0xFF,
301 : .year = -1,
302 : }
303 : };
304 199402 : continue;
305 : }
306 1800598 : date dt = timestamp_date(value);
307 1800598 : daytime tm = timestamp_daytime(value);
308 1800598 : int16_t year = date_year(dt);
309 1800598 : uint32_t ms = daytime_usec(tm);
310 1800598 : if (byteswap) {
311 0 : ms = copy_binary_byteswap32(ms);
312 0 : year = copy_binary_byteswap16(year);
313 : }
314 1800598 : *dst++ = (copy_binary_timestamp) {
315 : .time = {
316 : .ms = ms,
317 1800598 : .seconds = daytime_sec(tm),
318 1800598 : .minutes = daytime_min(tm),
319 1800598 : .hours = daytime_hour(tm),
320 : },
321 : .date = {
322 1800598 : .day = date_day(dt),
323 1800598 : .month = date_month(dt),
324 : .year = year,
325 : },
326 : };
327 : }
328 24 : return MAL_SUCCEED;
329 : }
330 :
331 : // Load NUL-terminated items from the stream and put them in the BAT.
332 : static str
333 23 : load_zero_terminated_text(BAT *bat, stream *s, int *eof_reached, int width, bool byteswap)
334 : {
335 23 : (void)byteswap;
336 23 : const char *mal_operator = "sql.importColumn";
337 23 : str msg = MAL_SUCCEED;
338 23 : bstream *bs = NULL;
339 23 : int tpe = BATttype(bat);
340 23 : void *buffer = NULL;
341 23 : size_t buffer_len = 0;
342 :
343 : // convert_and_validate_utf8() above counts on the following property to hold:
344 23 : assert(strNil((const char[2]){ 0x80, 0 }));
345 :
346 23 : bs = bstream_create(s, 1 << 20);
347 23 : if (bs == NULL) {
348 0 : msg = createException(SQL, "sql", SQLSTATE(HY013) MAL_MALLOC_FAIL);
349 0 : goto end;
350 : }
351 :
352 : // In the outer loop we refill the buffer until the stream ends.
353 : // In the inner loop we look for complete \0-terminated strings.
354 849 : while (1) {
355 436 : ssize_t nread = bstream_next(bs);
356 436 : if (nread < 0)
357 0 : bailout("%s", mnstr_peek_error(s));
358 436 : if (nread == 0)
359 : break;
360 :
361 418 : char *buf_start = &bs->buf[bs->pos];
362 418 : char *buf_end = &bs->buf[bs->len];
363 418 : char *start, *end;
364 14247341 : for (start = buf_start; (end = memchr(start, '\0', buf_end - start)) != NULL; start = end + 1) {
365 14246928 : char *value;
366 14246928 : if (!checkUTF8(start)) {
367 3 : msg = createException(SQL, "load_zero_terminated_text", SQLSTATE(42000) "malformed utf-8 byte sequence");
368 3 : goto end;
369 : }
370 14246925 : if (tpe == TYPE_str) {
371 10246923 : if (width > 0) {
372 2000006 : int w = UTF8_strwidth(start);
373 2000006 : if (w > width) {
374 1 : msg = createException(SQL, "sql.importColumn", "string too wide for column");
375 1 : goto end;
376 : }
377 : }
378 : value = start;
379 : } else {
380 4000002 : ssize_t n = ATOMfromstr(tpe, &buffer, &buffer_len, start, false);
381 4000002 : if (n <= 0) {
382 1 : msg = createException(SQL, "sql.importColumn", GDK_EXCEPTION);
383 1 : goto end;
384 : }
385 4000001 : value = buffer;
386 : }
387 14246923 : if (BUNappend(bat, value, false) != GDK_SUCCEED) {
388 0 : msg = createException(SQL, "sql.importColumn", GDK_EXCEPTION);
389 0 : goto end;
390 : }
391 : }
392 413 : bs->pos = start - buf_start;
393 : }
394 :
395 : // It's an error to have date left after falling out of the outer loop
396 18 : if (bs->pos < bs->len)
397 0 : bailout("unterminated string at end");
398 :
399 18 : end:
400 23 : *eof_reached = 0;
401 23 : GDKfree(buffer);
402 23 : if (bs != NULL) {
403 23 : *eof_reached = (int)bs->eof;
404 23 : bs->s = NULL;
405 23 : bstream_destroy(bs);
406 : }
407 23 : return msg;
408 : }
409 :
410 : static str
411 599 : dump_zero_terminated_text(BAT *bat, stream *s, BUN start, BUN length, bool byteswap)
412 : {
413 599 : (void)byteswap;
414 599 : const char *mal_operator = "sql.export_bin_column";
415 599 : str msg = MAL_SUCCEED;
416 599 : int tpe = BATttype(bat);
417 599 : assert(ATOMstorage(tpe) == TYPE_str); (void)tpe;
418 599 : assert(mnstr_isbinary(s));
419 :
420 :
421 599 : BUN end = start + length;
422 599 : assert(end <= BATcount(bat));
423 599 : BATiter bi = bat_iterator(bat);
424 10943231 : for (BUN p = start; p < end; p++) {
425 10942632 : const char *v = BUNtvar(bi, p);
426 10942632 : if (mnstr_writeStr(s, v) < 0 || mnstr_writeBte(s, 0) < 0) {
427 10942632 : bailout("%s", mnstr_peek_error(s));
428 : }
429 : }
430 :
431 599 : end:
432 599 : bat_iterator_end(&bi);
433 599 : return msg;
434 : }
435 :
436 : // Some streams, in particular the mapi upload stream, sometimes read fewer
437 : // bytes than requested. This function wraps the read in a loop to force it to
438 : // read the whole block
439 : static ssize_t
440 9996006 : read_exact(stream *s, void *buffer, size_t length)
441 : {
442 9996006 : char *p = buffer;
443 :
444 19992963 : while (length > 0) {
445 9996963 : ssize_t nread = mnstr_read(s, p, 1, length);
446 9996963 : if (nread < 0) {
447 0 : return nread;
448 9996963 : } else if (nread == 0) {
449 : break;
450 : } else {
451 9996957 : p += nread;
452 9996957 : length -= nread;
453 : }
454 : }
455 :
456 9996006 : return p - (char*)buffer;
457 : }
458 :
459 : // Read BLOBs. Every blob is preceded by a 64bit header word indicating its length.
460 : // NULLs are indicated by length==-1
461 : static str
462 6 : load_blob(BAT *bat, stream *s, int *eof_reached, int width, bool byteswap)
463 : {
464 6 : (void)width;
465 6 : const char *mal_operator = "sql.importColumn";
466 6 : str msg = MAL_SUCCEED;
467 6 : const blob *nil_value = ATOMnilptr(TYPE_blob);
468 6 : blob *buffer = NULL;
469 6 : size_t buffer_size = 0;
470 6 : union {
471 : uint64_t length;
472 : char bytes[8];
473 : } header;
474 :
475 6 : *eof_reached = 0;
476 :
477 6000006 : while (1) {
478 6000006 : const blob *value;
479 : // Read the header
480 6000006 : ssize_t nread = read_exact(s, header.bytes, 8);
481 6000006 : if (nread < 0) {
482 0 : bailout("%s", mnstr_peek_error(s));
483 6000006 : } else if (nread == 0) {
484 6 : *eof_reached = 1;
485 6 : break;
486 6000000 : } else if (nread < 8) {
487 0 : bailout("incomplete blob at end of file");
488 : }
489 6000000 : if (byteswap) {
490 2000000 : copy_binary_convert64(&header.length);
491 : }
492 :
493 6000000 : if (header.length == ~(uint64_t)0) {
494 : value = nil_value;
495 : } else {
496 4000002 : size_t length;
497 4000002 : size_t needed;
498 :
499 4000002 : if (header.length >= VAR_MAX) {
500 0 : bailout("blob too long");
501 : }
502 4000002 : length = (size_t) header.length;
503 :
504 : // Reallocate the buffer
505 4000002 : needed = sizeof(blob) + length;
506 4000002 : if (buffer_size < needed) {
507 : // do not use GDKrealloc, no need to copy the old contents
508 6 : GDKfree(buffer);
509 6 : size_t allocate = needed;
510 6 : allocate += allocate / 16; // add a little margin
511 : #ifdef _MSC_VER
512 : #pragma warning(suppress:4146)
513 : #endif
514 6 : allocate += ((~allocate + 1) % 0x100000); // round up to nearest MiB
515 6 : assert(allocate >= needed);
516 6 : buffer = GDKmalloc(allocate);
517 6 : if (!buffer) {
518 0 : msg = createException(SQL, "sql", SQLSTATE(HY013) MAL_MALLOC_FAIL);
519 0 : goto end;
520 : }
521 : buffer_size = allocate;
522 : }
523 :
524 : // Fill the buffer
525 4000002 : buffer->nitems = length;
526 4000002 : if (length > 0) {
527 3996000 : nread = read_exact(s, buffer->data, length);
528 3996000 : if (nread < 0) {
529 0 : bailout("%s", mnstr_peek_error(s));
530 3996000 : } else if ((size_t)nread < length) {
531 0 : bailout("Incomplete blob at end of file");
532 : }
533 : }
534 :
535 : value = buffer;
536 : }
537 :
538 6000000 : if (BUNappend(bat, value, false) != GDK_SUCCEED) {
539 0 : msg = createException(SQL, mal_operator, GDK_EXCEPTION);
540 0 : goto end;
541 : }
542 : }
543 :
544 6 : end:
545 6 : GDKfree(buffer);
546 6 : return msg;
547 : }
548 :
549 : static str
550 6 : dump_blob(BAT *bat, stream *s, BUN start, BUN length, bool byteswap)
551 : {
552 6 : const char *mal_operator = "sql.export_bin_column";
553 6 : str msg = MAL_SUCCEED;
554 6 : int tpe = BATttype(bat);
555 6 : assert(ATOMstorage(tpe) == TYPE_blob); (void)tpe;
556 6 : assert(mnstr_isbinary(s));
557 :
558 6 : BUN end = start + length;
559 6 : assert(end <= BATcount(bat));
560 6 : BATiter bi = bat_iterator(bat);
561 6 : uint64_t nil_header = ~(uint64_t)0;
562 6000006 : for (BUN p = start; p < end; p++) {
563 6000000 : const blob *b = BUNtvar(bi, p);
564 6000000 : uint64_t header = is_blob_nil(b) ? nil_header : (uint64_t)b->nitems;
565 6000000 : if (byteswap)
566 2000000 : copy_binary_convert64(&header);
567 6000000 : if (mnstr_write(s, &header, 8, 1) != 1) {
568 0 : bailout("%s", mnstr_peek_error(s));
569 : }
570 6000000 : if (!is_blob_nil(b) && mnstr_write(s, b->data,b->nitems, 1) != 1) {
571 6000000 : bailout("%s", mnstr_peek_error(s));
572 : }
573 : }
574 :
575 6 : end:
576 6 : bat_iterator_end(&bi);
577 6 : return msg;
578 :
579 : }
580 :
581 :
582 : static struct type_record_t type_recs[] = {
583 :
584 : // no conversion, no byteswapping
585 : { "bte", "bte", .encoder_trivial=true, .decoder_trivial=true},
586 : { "uuid", "uuid", .encoder_trivial=true, .decoder_trivial=true},
587 : { "bit", "bit", .encoder_trivial=true, .decoder_trivial=true, .validate=validate_bit },
588 :
589 : // vanilla integer types
590 : { "sht", "sht", .trivial_if_no_byteswap=true, .decoder=byteswap_sht, .encoder=byteswap_sht, .validate=validate_sht },
591 : { "int", "int", .trivial_if_no_byteswap=true, .decoder=byteswap_int, .encoder=byteswap_int, .validate=validate_int },
592 : { "lng", "lng", .trivial_if_no_byteswap=true, .decoder=byteswap_lng, .encoder=byteswap_lng, .validate=validate_lng },
593 : { "flt", "flt", .trivial_if_no_byteswap=true, .decoder=byteswap_flt, .encoder=byteswap_flt},
594 : { "dbl", "dbl", .trivial_if_no_byteswap=true, .decoder=byteswap_dbl, .encoder=byteswap_dbl},
595 : #ifdef HAVE_HGE
596 : { "hge", "hge", .trivial_if_no_byteswap=true, .decoder=byteswap_hge, .encoder=byteswap_hge, .validate=validate_hge },
597 : #endif
598 :
599 : { "blob", "blob", .loader=load_blob, .dumper=dump_blob },
600 :
601 : // \0-terminated text records
602 : { "str", "str", .loader=load_zero_terminated_text, .dumper=dump_zero_terminated_text },
603 : { "url", "url", .loader=load_zero_terminated_text, .dumper=dump_zero_terminated_text },
604 : { "json", "json", .loader=load_zero_terminated_text, .dumper=dump_zero_terminated_text },
605 :
606 : // temporal types have record size different from the underlying gdk type
607 : { "date", "date", .decoder=decode_date, .encoder=encode_date, .record_size=sizeof(copy_binary_date), },
608 : { "daytime", "daytime", .decoder=decode_time, .encoder=encode_time, .record_size=sizeof(copy_binary_time), },
609 : { "timestamp", "timestamp", .decoder=decode_timestamp, .encoder=encode_timestamp, .record_size=sizeof(copy_binary_timestamp), },
610 : };
611 :
612 :
613 : type_record_t*
614 3599 : find_type_rec(const char *name)
615 : {
616 3599 : struct type_record_t *end = (struct type_record_t*)((char *)type_recs + sizeof(type_recs));
617 23803 : for (struct type_record_t *t = &type_recs[0]; t < end; t++)
618 23803 : if (strcmp(t->method, name) == 0)
619 3599 : return t;
620 : return NULL;
621 : }
622 :
623 : bool
624 3232 : can_dump_binary_column(type_record_t *rec)
625 : {
626 3232 : return rec->encoder_trivial || rec->dumper || rec->encoder;
627 : }
|