Line data Source code
1 : /*
2 : * This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 : *
6 : * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
7 : */
8 :
9 : /*
10 : * author N.J. Nes, M.L. Kersten
11 : * 01/07/1996, 31/01/2002
12 : *
13 : * Input/Output module
14 : * The IO module provides simple @sc{ascii-io} rendering options.
15 : * It is modeled after the tuple formats, but does not
16 : * attempt to outline the results. Instead, it is geared at speed,
17 : * which also means that some functionality regarding the built-in
18 : * types is duplicated from the atoms definitions.
19 : *
20 : * A functional limited form of formatted printf is also provided.
21 : * It accepts at most one variable.
22 : * A more complete approach is the tablet module.
23 : *
24 : * The commands to load and save a BAT from/to an ASCII dump
25 : * are efficient, but work only for binary tables.
26 : */
27 :
28 : /*
29 : * Printing
30 : * The print commands are implemented as single instruction rules,
31 : * because they need access to the calling context.
32 : * At a later stage we can look into the issues related to
33 : * parsing the format string as part of the initialization phase.
34 : * The old method in V4 essentially causes a lot of overhead
35 : * because you have to prepare for the worst (e.g. mismatch format
36 : * identifier and argument value)
37 : * Beware, the types of the objects to be printed should be
38 : * obtained from the stack, because the symbol table may actually
39 : * allow for any type to be assigned.
40 : */
41 : #include "monetdb_config.h"
42 : #include "mal.h"
43 : #include "mal_instruction.h"
44 : #include "mal_interpreter.h"
45 : #include "mutils.h"
46 : #include "mal_exception.h"
47 :
48 : #define MAXFORMAT 64*1024
49 :
50 : static str
51 0 : io_stdin(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
52 : {
53 0 : bstream **ret= (bstream**) getArgReference(stk,pci,0);
54 : (void) mb;
55 0 : if( cntxt->fdin == NULL)
56 0 : throw(MAL, "io.print", SQLSTATE(HY002) "Input channel missing");
57 0 : *ret = cntxt->fdin;
58 0 : return MAL_SUCCEED;
59 : }
60 :
61 : static str
62 0 : io_stdout(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
63 : {
64 0 : stream **ret= (stream**) getArgReference(stk,pci,0);
65 : (void) mb;
66 0 : if( cntxt->fdout == NULL)
67 0 : throw(MAL, "io.print", SQLSTATE(HY002) "Output channel missing");
68 0 : *ret = cntxt->fdout;
69 0 : return MAL_SUCCEED;
70 : }
71 :
72 : static str
73 1410 : IOprintBoth(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci, int indx, str hd, str tl, int nobat)
74 : {
75 1410 : int tpe = getArgType(mb, pci, indx);
76 1410 : ptr val = getArgReference(stk, pci, indx);
77 1410 : stream *fp = cntxt->fdout;
78 :
79 : (void) mb;
80 1410 : if( cntxt->fdout == NULL)
81 0 : throw(MAL, "io.print", SQLSTATE(HY002) "Output channel missing");
82 :
83 1410 : if (tpe == TYPE_any)
84 1 : tpe = stk->stk[pci->argv[indx]].vtype;
85 1410 : if (val == NULL || tpe == TYPE_void) {
86 2 : if (hd)
87 2 : mnstr_printf(fp, "%s", hd);
88 2 : mnstr_printf(fp, "nil");
89 2 : if (tl)
90 2 : mnstr_printf(fp, "%s", tl);
91 2 : return MAL_SUCCEED;
92 : }
93 1408 : if (isaBatType(tpe) ) {
94 : BAT *b;
95 :
96 652 : if (is_bat_nil(*(bat *) val)) {
97 2 : if (hd)
98 2 : mnstr_printf(fp, "%s", hd);
99 2 : mnstr_printf(fp,"nil");
100 2 : if (tl)
101 2 : mnstr_printf(fp, "%s", tl);
102 2 : return MAL_SUCCEED;
103 : }
104 650 : b = BATdescriptor(*(bat *) val);
105 650 : if (b == NULL) {
106 0 : throw(MAL, "io.print", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
107 : }
108 650 : if (nobat) {
109 0 : if (hd)
110 0 : mnstr_printf(fp, "%s", hd);
111 0 : mnstr_printf(fp, "<%s>", BBP_logical(b->batCacheid));
112 0 : if (tl)
113 0 : mnstr_printf(fp, "%s", tl);
114 : } else {
115 650 : BATprint(cntxt->fdout, b);
116 : }
117 650 : BBPunfix(b->batCacheid);
118 650 : return MAL_SUCCEED;
119 : }
120 756 : if (hd)
121 756 : mnstr_printf(fp, "%s", hd);
122 :
123 756 : if (ATOMvarsized(tpe))
124 453 : ATOMprint(tpe, *(str *) val, fp);
125 : else
126 303 : ATOMprint(tpe, val, fp);
127 :
128 756 : if (tl)
129 755 : mnstr_printf(fp, "%s", tl);
130 : return MAL_SUCCEED;
131 : }
132 :
133 : static str
134 1409 : IOprint_val(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr p)
135 : {
136 : int i;
137 : str msg;
138 :
139 : (void) cntxt;
140 1409 : if (p->argc == 2)
141 1408 : msg = IOprintBoth(cntxt, mb, stk, p, 1, "[ ", " ]\n", 0);
142 : else {
143 1 : msg = IOprintBoth(cntxt, mb, stk, p, 1, "[ ", 0, 1);
144 1 : if (msg)
145 : return msg;
146 1 : for (i = 2; i < p->argc - 1; i++)
147 0 : if ((msg = IOprintBoth(cntxt,mb, stk, p, i, ", ", 0, 1)) != NULL)
148 0 : return msg;
149 1 : msg = IOprintBoth(cntxt,mb, stk, p, i, ", ", "]\n", 1);
150 : }
151 : return msg;
152 :
153 : }
154 :
155 : /*
156 : * The IOprintf_() gets a format str, and a sequence of (ptr,int) parameters
157 : * containing values and their type numbers. The printf() proved to be a
158 : * great risk; people formatting badly their "%s" format strings were crashing
159 : * the kernel. This function will prevent you from doing so.
160 : *
161 : * New implementation that repeatedly invokes sprintf => hacking the va_alist
162 : * for using vfsprintf proved to be too compiler-dependent (OLD approach).
163 : */
164 : #define writemem(X1) \
165 : do { \
166 : if (dst+X1 > buf+size) { \
167 : ptrdiff_t offset = dst - buf; \
168 : char *tmp; \
169 : do { \
170 : size *= 2; \
171 : } while (dst+X1 > buf+size); \
172 : tmp = GDKrealloc(buf, size); \
173 : if (tmp == NULL) { \
174 : va_end(ap); \
175 : GDKfree(buf); \
176 : GDKfree(add); \
177 : throw(MAL, "io.printf", SQLSTATE(HY013) MAL_MALLOC_FAIL); \
178 : } \
179 : buf = tmp; \
180 : dst = buf + offset; \
181 : } \
182 : } while (0)
183 :
184 : #define m5sprintf(X1)\
185 : if (width > adds) {\
186 : str newadd;\
187 : newadd = GDKrealloc(add, width + 10);\
188 : if (newadd != NULL) {\
189 : adds = width + 10;\
190 : add = newadd;\
191 : }\
192 : }\
193 : n = snprintf(add, adds, meta, X1);\
194 : while (n < 0 || (size_t) n >= adds) {\
195 : size_t newadds;\
196 : str newadd;\
197 : \
198 : if (n >= 0) /* glibc 2.1 */\
199 : newadds = n + 1; /* precisely what is needed */\
200 : else /* glibc 2.0 */\
201 : newadds = n * 2; /* twice the old size */\
202 : \
203 : newadd = GDKrealloc(add, newadds);\
204 : if (newadd == NULL)\
205 : break;\
206 : \
207 : adds = newadds;\
208 : add = newadd;\
209 : n = snprintf(add, adds, meta, X1);\
210 : }
211 :
212 :
213 : static char toofew_error[80] = OPERATION_FAILED " At least %d parameter(s) expected.\n";
214 : static char format_error[80] = OPERATION_FAILED " Error in format before param %d.\n";
215 : static char type_error[80] = OPERATION_FAILED " Illegal type in param %d.\n";
216 :
217 : #define return_error(x) \
218 : do { \
219 : GDKfree(buf); \
220 : GDKfree(add); \
221 : throw(MAL,"io.printf", x,argc); \
222 : } while (0)
223 :
224 : static char niltext[4] = "nil";
225 :
226 : static str
227 162 : IOprintf_(str *res, str format, ...)
228 : {
229 : va_list ap;
230 : int n;
231 :
232 : int prec = 0, dotseen = 0, escaped = 0, type, size, argc = 1;
233 : size_t adds = 100, width = 0;
234 : char *add, *dst, *buf, *cur, *paramseen = NULL;
235 : char *p;
236 :
237 162 : if (format == NULL) {
238 0 : throw(MAL,"io.printf", ILLEGAL_ARGUMENT " NULL pointer passed as format.\n");
239 162 : } else if (strchr(format, '%') == NULL) {
240 43 : *res = GDKstrdup(format);
241 43 : if (*res == NULL)
242 0 : throw(MAL,"io.printf", SQLSTATE(HY013) MAL_MALLOC_FAIL);
243 : return MAL_SUCCEED;
244 : }
245 119 : buf = dst = (str) GDKmalloc(size = 80);
246 119 : if ( buf == NULL)
247 0 : throw(MAL,"io.printf", SQLSTATE(HY013) MAL_MALLOC_FAIL);
248 119 : *res = NULL;
249 :
250 119 : add = GDKmalloc(adds);
251 119 : if (add == NULL) {
252 0 : GDKfree(buf);
253 0 : throw(MAL,"io.printf", SQLSTATE(HY013) MAL_MALLOC_FAIL);
254 : }
255 :
256 119 : va_start(ap,format);
257 1480 : for (cur = format; *cur; cur++) {
258 1362 : if (paramseen) {
259 : char meta[100];
260 : ptrdiff_t extra = 0;
261 : ptrdiff_t len;
262 :
263 141 : if (GDKisdigit(*cur)) {
264 10 : if (dotseen) {
265 2 : prec = 10 * prec + (*cur - '0');
266 : } else {
267 8 : width = 10 * width + (*cur - '0');
268 : }
269 15 : continue;
270 131 : } else if (dotseen == 0 && *cur == '.') {
271 : dotseen = 1;
272 2 : continue;
273 129 : } else if (cur == paramseen + 1 && (*cur == '+' || *cur == '-' || *cur == ' ')) {
274 3 : continue;
275 126 : } else if (*cur == 'l') {
276 0 : cur++;
277 0 : if (*cur == 'l') {
278 0 : cur++;
279 : /* start of ll */
280 0 : extra = (cur - paramseen) - 2;
281 : }
282 : }
283 126 : if ((p = va_arg(ap, char *)) == NULL) {
284 1 : va_end(ap);
285 1 : return_error(toofew_error);
286 : }
287 125 : type = va_arg(ap, int);
288 125 : type = ATOMbasetype(type);
289 :
290 125 : len = 1 + (cur - paramseen);
291 125 : memcpy(meta, paramseen, len);
292 125 : meta[len] = 0;
293 125 : if (ATOMcmp(type, ATOMnilptr(type), p) == 0) {
294 : /* value is nil; attempt to print formatted 'nil'
295 : without generating %ls etc. */
296 : char *csrc, *ctrg = meta;
297 :
298 39 : for (csrc = paramseen; csrc < cur; csrc++) {
299 23 : if (*csrc == '.')
300 : break;
301 22 : if (GDKisdigit(*csrc) || *csrc == '-')
302 4 : *(++ctrg) = *csrc;
303 : }
304 17 : *(++ctrg) = 's';
305 17 : *(++ctrg) = 0;
306 17 : m5sprintf(niltext);
307 108 : } else if (strchr("cdiouxX", *cur) && !extra) {
308 : int ival;
309 :
310 82 : if (dotseen) {
311 0 : va_end(ap);
312 0 : return_error(format_error);
313 82 : } else if (type == TYPE_bte) {
314 3 : ival = (int) *(bte *) p;
315 79 : } else if (type == TYPE_sht) {
316 0 : ival = (int) *(sht *) p;
317 79 : } else if (type == TYPE_flt) {
318 0 : ival = (int) *(flt *) p;
319 79 : } else if (type == TYPE_lng) {
320 23 : goto largetypes;
321 : #ifdef HAVE_HGE
322 56 : } else if (type == TYPE_hge) {
323 : /* Does this happen?
324 : * If so, what do we have TODO ? */
325 0 : va_end(ap);
326 0 : return_error(type_error);
327 : #endif
328 56 : } else if (type == TYPE_int) {
329 56 : ival = *(int *) p;
330 : } else {
331 0 : va_end(ap);
332 0 : return_error(type_error);
333 : }
334 59 : m5sprintf(ival);
335 26 : } else if (strchr("diouxX", *cur)) {
336 : #ifdef NATIVE_WIN32
337 : ptrdiff_t i;
338 : #endif
339 : lng lval;
340 :
341 0 : if (dotseen) {
342 0 : va_end(ap);
343 0 : return_error(format_error);
344 : }
345 0 : largetypes:
346 23 : if (type == TYPE_bte) {
347 0 : lval = (lng) *(bte *) p;
348 23 : } else if (type == TYPE_sht) {
349 0 : lval = (lng) *(sht *) p;
350 23 : } else if (type == TYPE_int) {
351 0 : lval = (lng) *(int *) p;
352 23 : } else if (type == TYPE_flt) {
353 0 : lval = (lng) *(flt *) p;
354 23 : } else if (type == TYPE_dbl) {
355 0 : lval = (lng) *(dbl *) p;
356 23 : } else if (type == TYPE_lng) {
357 23 : lval = *(lng *) p;
358 : #ifdef HAVE_HGE
359 0 : } else if (type == TYPE_hge) {
360 : /* Does this happen?
361 : * If so, what do we have TODO ? */
362 0 : va_end(ap);
363 0 : return_error(type_error);
364 : #endif
365 : } else {
366 0 : va_end(ap);
367 0 : return_error(type_error);
368 : }
369 23 : if (!extra) {
370 23 : meta[len + 2] = meta[len];
371 23 : meta[len + 1] = meta[len - 1];
372 23 : meta[len] = 'l';
373 23 : meta[len - 1] = 'l';
374 : len += 2;
375 : extra = len - 3;
376 : }
377 : #ifdef NATIVE_WIN32
378 : for (i = len; i >= (extra + 2); i--) {
379 : meta[i + 1] = meta[i];
380 : }
381 : meta[extra] = 'I';
382 : meta[extra + 1] = '6';
383 : meta[extra + 2] = '4';
384 : #endif
385 23 : m5sprintf(lval);
386 26 : } else if (strchr("feEgG", *cur)) {
387 : dbl dval;
388 :
389 2 : if (type == TYPE_flt) {
390 2 : dval = (dbl) *(flt *) p;
391 0 : } else if (type == TYPE_dbl) {
392 0 : dval = *(dbl *) p;
393 : } else {
394 0 : va_end(ap);
395 0 : return_error(type_error);
396 : }
397 2 : width += (1 + prec);
398 2 : m5sprintf(dval);
399 24 : } else if (*cur == 's') {
400 : size_t length;
401 :
402 24 : if (extra) {
403 0 : va_end(ap);
404 0 : return_error(format_error);
405 24 : } else if (type != TYPE_str) {
406 0 : va_end(ap);
407 0 : return_error(type_error);
408 : }
409 24 : length = strLen(p);
410 24 : width++;
411 24 : prec++; /* account for '\0' */
412 24 : if (dotseen && (size_t) prec < length)
413 : length = (size_t) prec;
414 : if (length > width)
415 : width = length;
416 25 : m5sprintf(p);
417 : } else {
418 0 : va_end(ap);
419 0 : return_error(format_error);
420 : }
421 125 : width = strlen(add);
422 125 : writemem(width);
423 125 : memcpy(dst, add, width);
424 125 : dst += width;
425 : paramseen = NULL;
426 125 : argc++;
427 1221 : } else if (!escaped) {
428 1221 : if (*cur == '\\' || (*cur == '%' && cur[1] == '%')) {
429 : escaped = 1;
430 1221 : } else if (*cur == '%') {
431 : paramseen = cur;
432 : dotseen = prec = 0;
433 : width = 0;
434 : } else {
435 1095 : writemem(1);
436 1095 : *dst++ = *cur;
437 : }
438 : } else {
439 : escaped = 0;
440 0 : writemem(1);
441 0 : *dst++ = *cur;
442 : }
443 : }
444 :
445 : /*
446 : if ( va_arg(ap, char *) != NULL){
447 : GDKfree(buf);
448 : throw(MAL,"io.printf", "params %d and beyond ignored %s.\n",argc);
449 : }
450 : */
451 :
452 118 : writemem(1);
453 118 : va_end(ap);
454 118 : *dst = 0;
455 118 : *res = buf;
456 118 : GDKfree(add);
457 118 : return MAL_SUCCEED;
458 : }
459 :
460 : #define getArgValue(s,p,k) VALptr(&(s)->stk[(p)->argv[k]])
461 :
462 : #define G(X) getArgValue(stk,pci,X), getArgType(mb,pci,X)
463 :
464 : static str
465 162 : IOprintf(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
466 : {
467 162 : str *fmt = getArgReference_str(stk,pci,1);
468 162 : str fmt2 = NULL;
469 : str msg= MAL_SUCCEED;
470 :
471 : (void) cntxt;
472 : (void) mb;
473 162 : switch( pci->argc){
474 43 : case 2: msg= IOprintf_(&fmt2,*fmt);
475 43 : break;
476 114 : case 3: msg= IOprintf_(&fmt2,*fmt,G(2));
477 114 : break;
478 4 : case 4: msg= IOprintf_(&fmt2,*fmt,G(2),G(3));
479 4 : break;
480 0 : case 5: msg= IOprintf_(&fmt2,*fmt,G(2),G(3),G(4));
481 0 : break;
482 1 : case 6: msg= IOprintf_(&fmt2,*fmt,G(2),G(3),G(4),G(5));
483 1 : break;
484 0 : case 7: msg= IOprintf_(&fmt2,*fmt,G(2),G(3),G(4),G(5),G(6));
485 0 : break;
486 0 : case 8: msg= IOprintf_(&fmt2,*fmt,G(2),G(3),G(4),G(5),G(6),G(7));
487 0 : break;
488 0 : case 9: msg= IOprintf_(&fmt2,*fmt,G(2),G(3),G(4),G(5),G(6),G(7),G(8));
489 0 : break;
490 0 : case 10: msg= IOprintf_(&fmt2,*fmt,G(2),G(3),G(4),G(5),G(6),G(7),G(8),G(9));
491 0 : break;
492 0 : default:
493 0 : throw(MAL, "io.printf", "Too many arguments to io.printf");
494 : }
495 162 : if (msg== MAL_SUCCEED) {
496 161 : mnstr_printf(cntxt->fdout,"%s",fmt2);
497 161 : GDKfree(fmt2);
498 : }
499 : return msg;
500 : }
501 : static str
502 0 : IOprintfStream(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci){
503 0 : str *fmt = getArgReference_str(stk,pci,2);
504 0 : str fmt2 = NULL;
505 0 : stream *f= (stream *) getArgReference(stk,pci,1);
506 : str msg= MAL_SUCCEED;
507 :
508 : (void) cntxt;
509 : (void) mb;
510 0 : switch( pci->argc){
511 0 : case 3: msg= IOprintf_(&fmt2,*fmt);
512 0 : break;
513 0 : case 4: msg= IOprintf_(&fmt2,*fmt,G(3));
514 0 : break;
515 0 : case 5: msg= IOprintf_(&fmt2,*fmt,G(3),G(4));
516 0 : break;
517 0 : case 6: msg= IOprintf_(&fmt2,*fmt,G(3),G(4),G(5));
518 0 : break;
519 0 : case 7: msg= IOprintf_(&fmt2,*fmt,G(3),G(4),G(5),G(6));
520 0 : break;
521 0 : case 8: msg= IOprintf_(&fmt2,*fmt,G(3),G(4),G(5),G(6),G(7));
522 0 : break;
523 0 : case 9: msg= IOprintf_(&fmt2,*fmt,G(3),G(4),G(5),G(6),G(7),G(8));
524 0 : break;
525 0 : case 10: msg= IOprintf_(&fmt2,*fmt,G(3),G(4),G(5),G(6),G(7),G(8),G(9));
526 0 : break;
527 0 : case 11: msg= IOprintf_(&fmt2,*fmt,G(3),G(4),G(5),G(6),G(7),G(8),G(9),G(10));
528 0 : break;
529 0 : default:
530 0 : throw(MAL, "io.printf", "Too many arguments to io.printf");
531 : }
532 0 : if (msg== MAL_SUCCEED){
533 0 : mnstr_printf(f,"%s",fmt2);
534 0 : GDKfree(fmt2);
535 : }
536 : return msg;
537 : }
538 :
539 : /*
540 : * The table printing routine implementations.
541 : * They merely differ in destination and order prerequisite
542 : */
543 : static str
544 55 : IOtable(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
545 : {
546 : BAT *piv[MAXPARAMS];
547 : int i;
548 : int tpe;
549 : ptr val;
550 :
551 : (void) cntxt;
552 55 : if ( pci->retc != 1 || pci->argc < 2 || pci->argc >= MAXPARAMS)
553 0 : throw(MAL, "io.table", "INTERNAL ERROR" " assertion error retc %d argc %d", pci->retc, pci->argc);
554 :
555 55 : memset(piv, 0, sizeof(BAT*) * MAXPARAMS);
556 183 : for (i = 1; i < pci->argc; i++) {
557 128 : tpe = getArgType(mb, pci, i);
558 128 : val = getArgReference(stk, pci, i);
559 128 : if (!isaBatType(tpe)) {
560 0 : while (--i >= 1)
561 0 : if (piv[i] != NULL)
562 0 : BBPunfix(piv[i]->batCacheid);
563 0 : throw(MAL, "io.table", ILLEGAL_ARGUMENT " BAT expected");
564 : }
565 128 : if ((piv[i] = BATdescriptor(*(bat *) val)) == NULL) {
566 0 : while (--i >= 1)
567 0 : BBPunfix(piv[i]->batCacheid);
568 0 : throw(MAL, "io.table", ILLEGAL_ARGUMENT " null BAT encountered");
569 : }
570 : }
571 : /* add materialized void column */
572 55 : piv[0] = BATdense(piv[1]->hseqbase, 0, BATcount(piv[1]));
573 55 : if (piv[0] == NULL) {
574 0 : for (i = 1; i < pci->argc; i++)
575 0 : BBPunfix(piv[i]->batCacheid);
576 0 : throw(MAL, "io.table", SQLSTATE(HY013) MAL_MALLOC_FAIL);
577 : }
578 55 : BATprintcolumns(cntxt->fdout, pci->argc, piv);
579 238 : for (i = 0; i < pci->argc; i++)
580 183 : BBPunfix(piv[i]->batCacheid);
581 : return MAL_SUCCEED;
582 : }
583 :
584 :
585 : /*
586 : * Bulk export/loading
587 : * To simplify conversion between versions and to interface with other
588 : * applications, we use a simple import/export operation.
589 : *
590 : * The conversion routine assumes space in the buffer for storing the result.
591 : */
592 : /*
593 : * A BAT can be saved in Monet format using the export command.
594 : * It is of particular use in preparing an ASCII version for migration.
595 : * The exported file is saved in the context of the directory
596 : * where the server was started unless an absolute file name was
597 : * presented.
598 : */
599 :
600 : static str
601 1 : IOexport(void *ret, bat *bid, str *fnme)
602 : {
603 : BAT *b;
604 : stream *s;
605 :
606 : (void) ret;
607 1 : if ((b = BATdescriptor(*bid)) == NULL)
608 0 : throw(MAL, "io.export", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
609 :
610 1 : s = open_wastream(*fnme);
611 1 : if (s == NULL ){
612 0 : BBPunfix(b->batCacheid);
613 0 : throw(MAL, "io.export", "%s", mnstr_peek_error(NULL));
614 : }
615 1 : if (mnstr_errnr(s)) {
616 0 : mnstr_close(s);
617 0 : BBPunfix(b->batCacheid);
618 0 : throw(MAL, "io.export", "%s", mnstr_peek_error(NULL));
619 : }
620 1 : BATprintcolumns(s, 1, &b);
621 1 : close_stream(s);
622 1 : BBPunfix(b->batCacheid);
623 1 : return MAL_SUCCEED;
624 : }
625 :
626 : /*
627 : * The import command reads a single BAT from an ASCII file produced by export.
628 : */
629 : static str
630 1 : IOimport(void *ret, bat *bid, str *fnme)
631 : {
632 : BAT *b;
633 : ssize_t (*tconvert) (const char *, size_t *, ptr *, bool);
634 : ssize_t n;
635 : size_t bufsize = 2048; /* NIELS:tmp change used to be 1024 */
636 : char *base, *cur, *end;
637 : char *buf;
638 1 : ptr t = 0;
639 1 : size_t lt = 0;
640 1 : FILE *fp = MT_fopen(*fnme, "r");
641 : char msg[BUFSIZ];
642 :
643 : (void) ret;
644 1 : if ((b = BATdescriptor(*bid)) == NULL) {
645 0 : if (fp)
646 0 : fclose(fp);
647 0 : throw(MAL, "io.import", SQLSTATE(HY002) RUNTIME_OBJECT_MISSING);
648 : }
649 :
650 1 : tconvert = BATatoms[BATttype(b)].atomFromStr;
651 : /*
652 : * Open the file. Memory map it to minimize buffering problems.
653 : */
654 1 : if (fp == NULL) {
655 0 : BBPunfix(b->batCacheid);
656 0 : throw(MAL, "io.import", RUNTIME_FILE_NOT_FOUND ":%s", *fnme);
657 : } else {
658 : int fn;
659 : struct stat st;
660 :
661 1 : buf = (char *) GDKmalloc(bufsize);
662 1 : if ( buf == NULL) {
663 0 : BBPunfix(b->batCacheid);
664 0 : fclose(fp);
665 0 : throw(MAL,"io.import", SQLSTATE(HY013) MAL_MALLOC_FAIL);
666 : }
667 :
668 1 : if ((fn = fileno(fp)) <= 0) {
669 0 : BBPunfix(b->batCacheid);
670 0 : fclose(fp);
671 0 : GDKfree(buf);
672 0 : throw(MAL, "io.import", OPERATION_FAILED ": fileno()");
673 : }
674 1 : if (fstat(fn, &st) != 0) {
675 0 : BBPunfix(b->batCacheid);
676 0 : fclose(fp);
677 0 : GDKfree(buf);
678 0 : throw(MAL, "io.import", OPERATION_FAILED ": fstat()");
679 : }
680 :
681 1 : (void) fclose(fp);
682 1 : if (st.st_size <= 0) {
683 0 : BBPunfix(b->batCacheid);
684 0 : GDKfree(buf);
685 0 : throw(MAL, "io.import", OPERATION_FAILED ": empty file");
686 : }
687 : #if SIZEOF_SIZE_T == SIZEOF_INT
688 : if (st.st_size > 0x7FFFFFFF) {
689 : BBPunfix(b->batCacheid);
690 : GDKfree(buf);
691 : throw(MAL, "io.import", OPERATION_FAILED ": file too large");
692 : }
693 : #endif
694 1 : base = cur = (char *) GDKmmap(*fnme, MMAP_SEQUENTIAL, (size_t) st.st_size);
695 1 : if (cur == NULL) {
696 0 : BBPunfix(b->batCacheid);
697 0 : GDKfree(buf);
698 0 : throw(MAL, "io.import", OPERATION_FAILED "GDKmmap()");
699 : }
700 1 : end = cur + st.st_size;
701 :
702 : }
703 : /* Parse a line. Copy it into a buffer. Concat broken lines with a slash. */
704 7 : while (cur < end) {
705 : str dst = buf, src = cur, p;
706 : size_t l;
707 :
708 : /* like p = strchr(cur, '\n') but with extra bounds check */
709 115 : for (p = cur; p < end && *p != '\n'; p++)
710 : ;
711 6 : l = p - cur;
712 :
713 6 : if (p < end) {
714 6 : while (src[l - 1] == '\\') {
715 0 : if (buf+bufsize < dst+l) {
716 0 : size_t len = dst - buf;
717 0 : size_t inc = (size_t) ((dst+l) - buf);
718 0 : char *tmp = GDKrealloc(buf, bufsize = MAX(inc,bufsize)*2);
719 0 : if (tmp == NULL) {
720 0 : BBPunfix(b->batCacheid);
721 0 : GDKfree(buf);
722 0 : GDKfree(t);
723 0 : GDKmunmap(base, end - base);
724 0 : throw(MAL, "io.import", SQLSTATE(HY013) MAL_MALLOC_FAIL);
725 : }
726 : buf = tmp;
727 0 : dst = buf + len;
728 : }
729 0 : memcpy(dst, src, l-1);
730 0 : dst += l - 1;
731 0 : src += l + 1;
732 0 : for (p = src; p < end && *p != '\n'; p++)
733 : ;
734 0 : if (p == end)
735 : break;
736 0 : l = p - src;
737 : }
738 : }
739 :
740 6 : if (buf+bufsize < dst+l) {
741 0 : size_t len = dst - buf;
742 0 : size_t inc = (size_t) ((dst+l) - buf);
743 0 : char *tmp = GDKrealloc(buf, bufsize = MAX(inc,bufsize)*2);
744 0 : if (tmp == NULL) {
745 0 : BBPunfix(b->batCacheid);
746 0 : GDKfree(buf);
747 0 : GDKfree(t);
748 0 : GDKmunmap(base, end - base);
749 0 : throw(MAL, "io.import", SQLSTATE(HY013) MAL_MALLOC_FAIL);
750 : }
751 : buf = tmp;
752 0 : dst = buf + len;
753 : }
754 6 : memcpy(dst, src, l);
755 6 : dst[l] = 0;
756 6 : cur = p + 1;
757 : /* Parse the line, and insert a BUN. */
758 6 : for (p = buf; *p && GDKisspace(*p); p++)
759 : ;
760 6 : if (*p == '#')
761 4 : continue;
762 :
763 2 : for (;*p && *p != '['; p++)
764 : ;
765 2 : if (*p)
766 4 : for (p++; *p && GDKisspace(*p); p++)
767 : ;
768 2 : if (*p == 0) {
769 0 : BBPunfix(b->batCacheid);
770 0 : snprintf(msg,sizeof(msg),"error in input %s",buf);
771 0 : GDKfree(buf);
772 0 : GDKmunmap(base, end - base);
773 0 : GDKfree(t);
774 0 : throw(MAL, "io.import", "%s", msg);
775 : }
776 2 : n = tconvert(p, <, (ptr*)&t, true);
777 2 : if (n < 0) {
778 0 : BBPunfix(b->batCacheid);
779 0 : snprintf(msg,sizeof(msg),"error in input %s",buf);
780 0 : GDKfree(buf);
781 0 : GDKmunmap(base, end - base);
782 0 : GDKfree(t);
783 0 : throw(MAL, "io.import", "%s", msg);
784 : }
785 : p += n;
786 2 : if (BUNappend(b, t, false) != GDK_SUCCEED) {
787 0 : BBPunfix(b->batCacheid);
788 0 : GDKfree(buf);
789 0 : GDKfree(t);
790 0 : GDKmunmap(base, end - base);
791 0 : throw(MAL, "io.import", "insert failed");
792 : }
793 :
794 : #if 0 /* why do this? any measured effects? */
795 : /*
796 : * Unmap already parsed memory, to keep the memory usage low.
797 : */
798 : #ifndef WIN32
799 : #define MAXBUF 40*MT_pagesize()
800 : if ((unsigned) (cur - base) > MAXBUF) {
801 : GDKmunmap(base, MAXBUF);
802 : base += MAXBUF;
803 : }
804 : #endif
805 : #endif
806 : }
807 : /* Cleanup and exit. Return the filled BAT. */
808 1 : if (t)
809 1 : GDKfree(t);
810 1 : GDKfree(buf);
811 1 : GDKmunmap(base, end - base);
812 1 : BBPunfix(b->batCacheid);
813 1 : return MAL_SUCCEED;
814 : }
815 :
816 :
817 :
818 : static str
819 0 : IOsetmallocsuccesscount(void *res, lng *count) {
820 : (void) res;
821 0 : GDKsetmallocsuccesscount(*count);
822 0 : return MAL_SUCCEED;
823 : }
824 :
825 : #include "mel.h"
826 : mel_func mal_io_init_funcs[] = {
827 : pattern("io", "stdin", io_stdin, false, "return the input stream to the database client", args(1,1, arg("",bstream))),
828 : pattern("io", "stdout", io_stdout, false, "return the output stream for the database client", args(1,1, arg("",streams))),
829 : pattern("io", "print", IOprint_val, false, "Print a MAL value tuple .", args(1,3, arg("",void),argany("val",1),varargany("lst",0))),
830 : pattern("io", "print", IOtable, false, "BATs are printed with '#' for legend \nlines, and the BUNs on seperate lines \nbetween brackets, containing each to \ncomma separated values (head and tail). \nIf multiple BATs are passed for printing, \nprint() performs an implicit natural \njoin on the void head, producing a multi attribute table.", args(1,2, arg("",void),batvarargany("b1",0))),
831 : pattern("io", "print", IOprint_val, false, "Print a MAL value.", args(1,2, arg("",void),argany("val",1))),
832 : pattern("io", "print", IOprint_val, false, "Print a MAL value column .", args(1,2, arg("",void),batargany("val",1))),
833 : pattern("io", "printf", IOprintf, false, "Select default format ", args(1,3, arg("",void),arg("fmt",str),varargany("val",0))),
834 : pattern("io", "printf", IOprintf, false, "Select default format ", args(1,2, arg("",void),arg("fmt",str))),
835 : pattern("io", "printf", IOprintfStream, false, "Select default format ", args(1,4, arg("",void),arg("filep",streams),arg("fmt",str),varargany("val",0))),
836 : pattern("io", "printf", IOprintfStream, false, "Select default format ", args(1,3, arg("",void),arg("filep",streams),arg("fmt",str))),
837 : command("io", "export", IOexport, false, "Export a BAT as ASCII to a file. If the 'filepath' is not absolute, it\nis put into the $DBPATH directory. Success of failure is indicated.", args(0,2, batargany("b",2),arg("filepath",str))),
838 : command("io", "import", IOimport, false, "Import a BAT from an ASCII dump. The tuples are appended to the\nfirst argument. Its signature must match the dump,\nelse parsing errors will occur as an exception.", args(0,2, batargany("b",2),arg("filepath",str))),
839 : command("io", "setmallocsuccesscount", IOsetmallocsuccesscount, false, "Set number of mallocs that are allowed to succeed.", args(1,2, arg("",void),arg("count",lng))),
840 : { .imp=NULL }
841 : };
842 : #include "mal_import.h"
843 : #ifdef _MSC_VER
844 : #undef read
845 : #pragma section(".CRT$XCU",read)
846 : #endif
847 257 : LIB_STARTUP_FUNC(init_mal_io_mal)
848 257 : { mal_module("mal_io", NULL, mal_io_init_funcs); }
|