LCOV - code coverage report
Current view: top level - sql/backends/monet5/vaults/csv - csv.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 232 296 78.4 %
Date: 2024-10-03 20:03:20 Functions: 18 18 100.0 %

          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             : #include "rel_file_loader.h"
      15             : #include "rel_exp.h"
      16             : 
      17             : #include "mal_instruction.h"
      18             : #include "mal_interpreter.h"
      19             : #include "mal_parser.h"
      20             : #include "mal_builder.h"
      21             : #include "mal_namespace.h"
      22             : #include "mal_exception.h"
      23             : #include "mal_linker.h"
      24             : #include "mal_backend.h"
      25             : #include "sql_types.h"
      26             : #include "rel_bin.h"
      27             : 
      28             : #include <unistd.h>
      29             : 
      30             : static stream *
      31          28 : csv_open_file(char* filename)
      32             : {
      33          28 :         return open_rastream(filename);
      34             : }
      35             : 
      36             : /* todo handle escapes */
      37             : static const char *
      38         286 : next_delim(const char *s, const char *e, char delim, char quote)
      39             : {
      40         286 :         bool inquote = false;
      41        2228 :         for(;  s < e; s++) {
      42        2116 :                 if (*s == quote)
      43         304 :                         inquote = !inquote;
      44        1812 :                 else if (!inquote && *s == delim)
      45         174 :                         return s;
      46             :         }
      47         112 :         if (s <= e)
      48         112 :                 return s;
      49             :         return NULL;
      50             : }
      51             : 
      52             : /* todo detect escapes */
      53             : static char
      54          14 : detect_quote(const char *buf)
      55             : {
      56          14 :         const char *cur = buf;
      57          14 :         const char *l = NULL;
      58             :         /* "'(none) */
      59          14 :         bool has_double_quote = true, has_single_quote = true;
      60          52 :         while ((has_double_quote || has_single_quote) && (l = strchr(cur, '\n')) != NULL) {
      61          38 :                 const char *s = cur, *t;
      62          38 :                 if (has_double_quote && ((t = strchr(s, '"')) == NULL || t > l))  /* no quote not used */
      63           6 :                         has_double_quote = false;
      64          38 :                 if (has_single_quote && ((t = strchr(s, '\'')) == NULL || t > l))  /* no quote not used */
      65          38 :                         has_single_quote = false;
      66          38 :                 cur = l+1;
      67             :         }
      68          14 :         if (has_double_quote && !has_single_quote)
      69             :                 return '"';
      70           6 :         if (has_single_quote && !has_double_quote)
      71           0 :                 return '\'';
      72             :         /* no quote */
      73             :         return '\0';
      74             : }
      75             : 
      76             : #define DLEN 4
      77             : static char
      78          14 : detect_delimiter(const char *buf, char q, int *nr_fields)
      79             : {
      80          14 :         const char delimiter[] = ",|;\t";
      81          14 :         int cnts[DLEN][2] = { 0 }, l = 0;
      82             : 
      83          14 :         const char *cur = buf;
      84             : 
      85          42 :         for (l = 0; l < 2; l++) { /* start with 2 lines only */
      86          28 :                 const char *e = strchr(cur, '\n');
      87          28 :                 if (!e)
      88             :                         break;
      89             :                 int i = 0;
      90             :                 const char *dp = delimiter;
      91         140 :                 for (char d = *dp; d; d=*(++dp), i++) {
      92         112 :                         const char *s = cur;
      93             :                         /* all lines should have some numbers */
      94         112 :                         if (l && cnts[i][l])
      95           0 :                                 if (cnts[i][0] != cnts[i][1])
      96             :                                         break;
      97             :                         int nr = 1;
      98         172 :                         while( (s = next_delim(s, e, d, q)) != NULL && s<e ) {
      99          60 :                                 if (s+1 <= e)
     100          60 :                                         nr++;
     101             :                                 s++;
     102             :                         }
     103         112 :                         cnts[i][l] = nr;
     104             :                 }
     105          28 :                 cur = e+1;
     106             :         }
     107          14 :         if (l) {
     108             :                 int maxpos = -1, maxcnt = 0;
     109          70 :                 for (int i = 0; i<DLEN; i++) {
     110          56 :                         if (cnts[i][0] == cnts[i][1] && maxcnt < cnts[i][0]) {
     111          56 :                                 maxcnt = cnts[i][0];
     112          56 :                                 maxpos = i;
     113             :                         }
     114             :                 }
     115          14 :                 if (maxpos>=0) {
     116          14 :                         *nr_fields = maxcnt;
     117          14 :                         return delimiter[maxpos];
     118             :                 }
     119             :         }
     120             :         /* nothing detected */
     121             :         return ' ';
     122             : }
     123             : 
     124             : typedef enum csv {
     125             :         CSV_NULL = 0,
     126             :         CSV_BOOLEAN,
     127             :         CSV_BIGINT,
     128             :         CSV_DECIMAL,
     129             :         CSV_DOUBLE,
     130             :         CSV_TIME,
     131             :         CSV_DATE,
     132             :         CSV_TIMESTAMP,
     133             :         CSV_STRING,
     134             :         //later: UUID, INET, JSON, URL etc
     135             : } csv_types_t;
     136             : 
     137             : typedef struct csv_type {
     138             :         csv_types_t type;
     139             :         int scale;
     140             : } csv_type;
     141             : 
     142             : static char*
     143          44 : csv_type_map(csv_type ct)
     144             : {
     145          44 :         switch(ct.type) {
     146             :         case CSV_NULL:
     147             :                 return  "null";
     148           0 :         case CSV_BOOLEAN:
     149           0 :                 return  "boolean";
     150          22 :         case CSV_BIGINT:
     151          22 :                 return  "bigint";
     152           0 :         case CSV_DECIMAL:
     153           0 :                 return  "decimal";
     154           0 :         case CSV_DOUBLE:
     155           0 :                 return  "double";
     156           0 :         case CSV_TIME:
     157           0 :                 return  "time";
     158           0 :         case CSV_DATE:
     159           0 :                 return  "date";
     160           0 :         case CSV_TIMESTAMP:
     161           0 :                 return  "timestamp";
     162             :         case CSV_STRING:
     163             :                 return  "varchar";
     164             :         }
     165             :         return  "varchar";
     166             : }
     167             : 
     168             : static bool
     169         164 : detect_null(const char *s, const char *e)
     170             : {
     171         164 :         if (e == s)
     172          16 :                 return true;
     173             :         /* TODO parse NULL value(s) */
     174             :         return false;
     175             : }
     176             : 
     177             : static bool
     178         148 : detect_bool(const char *s, const char *e)
     179             : {
     180         148 :         if ((e - s) == 1 && (*s == 'T' || *s == 't' || *s == 'F' || *s == 'f'))
     181             :                 return true;
     182         148 :         if (strcmp(s,"TRUE") == 0 || strcmp(s,"true") == 0 || strcmp(s,"FALSE") == 0 || strcmp(s,"false") == 0)
     183             :                 return true;
     184         148 :         if (strcmp(s,"NULL") == 0)
     185           0 :                 return true;
     186             :         return false;
     187             : }
     188             : 
     189             : static bool
     190         148 : detect_bigint(const char *s, const char *e)
     191             : {
     192         148 :         if (s[0] == '-' || s[0] == '+')
     193           0 :                 s++;
     194         390 :         while(s < e) {
     195         316 :                 if (!isdigit(*s))
     196             :                         break;
     197         242 :                 s++;
     198             :         }
     199         148 :         if (s==e)
     200          74 :                 return true;
     201             :         return false;
     202             : }
     203             : 
     204             : static bool
     205          74 : detect_decimal(const char *s, const char *e, int *scale)
     206             : {
     207          74 :         int dotseen = 0;
     208             : 
     209          74 :         if (s[0] == '-' || s[0] == '+')
     210           0 :                 s++;
     211          74 :         while(s < e) {
     212          74 :                 if (!dotseen && *s == '.')
     213           0 :                         dotseen = (int)(e-(s+1));
     214          74 :                 else if (!isdigit(*s))
     215             :                         break;
     216           0 :                 s++;
     217             :         }
     218          74 :         if (s==e && dotseen) {
     219           0 :                 *scale = dotseen;
     220           0 :                 return true;
     221             :         }
     222             :         return false;
     223             : }
     224             : 
     225             : static bool
     226          74 : detect_time(const char *s, const char *e)
     227             : {
     228             :         /* TODO detect time with timezone */
     229          74 :         if ((e-s) != 5)
     230             :                 return false;
     231             :         /* 00:00 - 23:59 */
     232           0 :         if (s[2] != ':')
     233             :                 return false;
     234           0 :         if ((((s[0] == '0' || s[0] == '1') &&
     235           0 :               (s[1] >= '0' && s[1] <= '9'))  ||
     236           0 :               (s[0] == '2' && (s[1] >= '0' && s[1] <= '3'))) &&
     237           0 :                 (s[3] >= '0' && s[3] <= '5' && s[4] >= '0' && s[4] <= '9'))
     238           0 :                 return true;
     239             :         return false;
     240             : }
     241             : 
     242             : static bool
     243          74 : detect_date(const char *s, const char *e)
     244             : {
     245          74 :         if ((e-s) != 10)
     246             :                 return false;
     247             :         /* YYYY-MM-DD */
     248           0 :         if ( s[4] == '-' && s[7] == '-' &&
     249           0 :            ((s[5] == '0' && s[6] >= '0' && s[6] <= '9') ||
     250           0 :             (s[5] == '1' && s[6] >= '0' && s[6] <= '2')) &&
     251           0 :             (s[8] >= '0' && s[8] <= '3' && s[9] >= '0' && s[9] <= '9'))
     252           0 :                 return true;
     253             :         return false;
     254             : }
     255             : 
     256             : static bool
     257          74 : detect_timestamp(const char *s, const char *e)
     258             : {
     259          74 :         if ((e-s) != 16)
     260             :                 return false;
     261             :         /* DATE TIME */
     262           0 :         if (detect_date(s, s+5) && detect_time(s+6, e))
     263             :                 return true;
     264             :         return false;
     265             : }
     266             : 
     267             : /* per row */
     268             : static  csv_type *
     269          50 : detect_types_row(const char *s, const char *e, char delim, char quote, int nr_fields)
     270             : {
     271          50 :         csv_type *types = (csv_type*)GDKmalloc(sizeof(csv_type)*nr_fields);
     272          50 :         if (!types)
     273             :                 return NULL;
     274         214 :         for(int i = 0; i< nr_fields; i++) {
     275         164 :                 const char *n = (i<nr_fields-1)?next_delim(s, e, delim, quote):e;
     276         164 :                 int scale = 0;
     277             : 
     278         164 :                 types[i].type = CSV_STRING;
     279         164 :                 if (n) {
     280         164 :                         if (detect_null(s,n))
     281          16 :                                 types[i].type = CSV_NULL;
     282         148 :                         else if (detect_bool(s,n))
     283           0 :                                 types[i].type = CSV_BOOLEAN;
     284         148 :                         else if (detect_bigint(s, n))
     285          74 :                                 types[i].type = CSV_BIGINT;
     286          74 :                         else if (detect_decimal(s, n, &scale))
     287           0 :                                 types[i].type = CSV_DECIMAL;
     288          74 :                         else if (detect_time(s, n))
     289           0 :                                 types[i].type = CSV_TIME;
     290          74 :                         else if (detect_date(s, n))
     291           0 :                                 types[i].type = CSV_DATE;
     292          74 :                         else if (detect_timestamp(s, n))
     293           0 :                                 types[i].type = CSV_TIMESTAMP;
     294         164 :                         types[i].scale = scale;
     295             :                 }
     296         164 :                 s = n+1;
     297             :         }
     298             :         return types;
     299             : }
     300             : 
     301             : static csv_type *
     302          14 : detect_types(const char *buf, char delim, char quote, int nr_fields, bool *has_header)
     303             : {
     304          14 :         const char *cur = buf;
     305          14 :         csv_type *types = NULL;
     306          14 :         int nr_lines = 0;
     307             : 
     308         114 :         while ( true ) {
     309          64 :                 const char *e = strchr(cur, '\n');
     310             : 
     311          64 :                 if (!e)
     312             :                         break;
     313          50 :                 csv_type *ntypes = detect_types_row( cur, e, delim, quote, nr_fields);
     314          50 :                 if (!ntypes)
     315             :                         return NULL;
     316          50 :                 cur = e+1;
     317          50 :                 int i = 0;
     318          50 :                 if (!types) {
     319          14 :                         for(i = 0; i<nr_fields && ntypes[i].type == CSV_STRING; i++)  ;
     320             : 
     321          14 :                         if (i == nr_fields)
     322           0 :                                 *has_header = true;
     323             :                 } else { /* check if all are string, then no header */
     324          36 :                         for(i = 0; i<nr_fields && ntypes[i].type == CSV_STRING; i++)  ;
     325             : 
     326          36 :                         if (i == nr_fields)
     327           0 :                                 *has_header = false;
     328             :                 }
     329          50 :                 if (nr_lines == 1)
     330          58 :                         for(i = 0; i<nr_fields; i++)
     331          44 :                                 types[i] = ntypes[i];
     332          50 :                 if (nr_lines > 1) {
     333          98 :                         for(i = 0; i<nr_fields; i++) {
     334          76 :                                 if (types[i].type == ntypes[i].type && types[i].type == CSV_DECIMAL && types[i].scale != ntypes[i].scale) {
     335           0 :                                         types[i].type = CSV_DOUBLE;
     336           0 :                                         types[i].scale = 0;
     337          76 :                                 } else if (types[i].type < ntypes[i].type)
     338          16 :                                         types[i] = ntypes[i];
     339             :                         }
     340             :                 }
     341          50 :                 if (types)
     342          36 :                         GDKfree(ntypes);
     343             :                 else
     344             :                         types = ntypes;
     345          50 :                 nr_lines++;
     346             :         }
     347          14 :         if (types) { /* NULL -> STRING */
     348          58 :                 for(int i = 0; i<nr_fields; i++) {
     349          44 :                         if (types[i].type == CSV_NULL)
     350           0 :                                 types[i].type = CSV_STRING;
     351             :                 }
     352             :         }
     353             :         return types;
     354             : }
     355             : 
     356             : static const char *
     357          44 : get_name(allocator *sa, const char *s, const char *es, const char **E, char delim, char quote, bool has_header, int col)
     358             : {
     359          44 :         if (!has_header) {
     360          44 :                 char buff[25];
     361          44 :                 snprintf(buff, 25, "name_%i", col);
     362          44 :                 return SA_STRDUP(sa, buff);
     363             :         } else {
     364           0 :                 const char *e = next_delim(s, es, delim, quote);
     365           0 :                 if (e) {
     366           0 :                         char *end = (char*)e;
     367           0 :                         if (s[0] == quote) {
     368           0 :                                 s++;
     369           0 :                                 end--;
     370             :                         }
     371           0 :                         end[0] = 0;
     372           0 :                         *E = e+1;
     373           0 :                         return SA_STRDUP(sa, s);
     374             :                 }
     375             :         }
     376             :         return NULL;
     377             : }
     378             : 
     379             : typedef struct csv_t {
     380             :         char sname[1];
     381             :         char quote;
     382             :         char delim;
     383             :         bool has_header;
     384             :         bool extra_tsep;
     385             : } csv_t;
     386             : 
     387             : /*
     388             :  * returns an error string (static or via tmp sa_allocator allocated), NULL on success
     389             :  *
     390             :  * Extend the subfunc f with result columns, ie.
     391             :         f->res = typelist;
     392             :         f->coltypes = typelist;
     393             :         f->colnames = nameslist; use tname if passed, for the relation name
     394             :  * Fill the list res_exps, with one result expressions per resulting column.
     395             :  */
     396             : static str
     397          28 : csv_relation(mvc *sql, sql_subfunc *f, char *filename, list *res_exps, char *tname)
     398             : {
     399          28 :         stream *file = csv_open_file(filename);
     400          28 :         char buf[8196+1];
     401             : 
     402          28 :         if(file == NULL)
     403             :                 return RUNTIME_FILE_NOT_FOUND;
     404             : 
     405             :         /*
     406             :          * detect delimiter ;|,\t  using quote \" or \' or none TODO escape \"\'\\ or none
     407             :          * detect types
     408             :          * detect header
     409             :          */
     410          14 :         ssize_t l = mnstr_read(file, buf, 1, 8196);
     411          14 :         mnstr_close(file);
     412          14 :         mnstr_destroy(file);
     413          14 :         if (l<0)
     414             :                 return RUNTIME_LOAD_ERROR;
     415          14 :         buf[l] = 0;
     416          14 :         bool has_header = false, extra_tsep = false;
     417          14 :         int nr_fields = 0;
     418          14 :         char q = detect_quote(buf);
     419          14 :         char d = detect_delimiter(buf, q, &nr_fields);
     420          14 :         csv_type *types = detect_types(buf, d, q, nr_fields, &has_header);
     421             : 
     422          14 :         if (!tname)
     423           0 :                 tname = "csv";
     424             : 
     425          14 :         f->tname = tname;
     426             : 
     427          14 :         const char *p = buf, *ep = strchr(p, '\n');;
     428          14 :         list *typelist = sa_list(sql->sa);
     429          14 :         list *nameslist = sa_list(sql->sa);
     430          58 :         for(int col = 0; col < nr_fields; col++) {
     431          44 :                 const char *name = get_name(sql->sa, p, ep, &p, d, q, has_header, col);
     432          44 :                 append(nameslist, (char*)name);
     433          44 :                 char* st = csv_type_map(types[col]);
     434             : 
     435          44 :                 if(st) {
     436          88 :                         sql_subtype *t = (types[col].type == CSV_DECIMAL)?
     437          44 :                                         sql_bind_subtype(sql->sa, st, 18, types[col].scale):
     438          44 :                                         sql_bind_subtype(sql->sa, st,  0, types[col].scale);
     439          44 :                         if (!t && (col+1) == nr_fields && types[col].type == CSV_NULL) {
     440           0 :                                 nr_fields--;
     441           0 :                                 extra_tsep = true;
     442           0 :                         } else if (t) {
     443          44 :                                 list_append(typelist, t);
     444          44 :                                 sql_exp *ne = exp_column(sql->sa, tname, name, t, CARD_MULTI, 1, 0, 0);
     445          44 :                                 set_basecol(ne);
     446          44 :                                 ne->alias.label = -(sql->nid++);
     447          44 :                                 list_append(res_exps, ne);
     448             :                         } else {
     449           0 :                                 GDKfree(types);
     450           0 :                                 throw(SQL, SQLSTATE(42000), "csv" "type %s not found\n", st);
     451             :                         }
     452             :                 } else {
     453             :                         /* shouldn't be possible, we fallback to strings */
     454           0 :                         GDKfree(types);
     455           0 :                         throw(SQL, SQLSTATE(42000), "csv" "type unknown\n");
     456             :                 }
     457             :         }
     458          14 :         GDKfree(types);
     459          14 :         f->res = typelist;
     460          14 :         f->coltypes = typelist;
     461          14 :         f->colnames = nameslist;
     462             : 
     463          14 :         csv_t *r = (csv_t *)sa_alloc(sql->sa, sizeof(csv_t));
     464          14 :         r->sname[0] = 0;
     465          14 :         r->quote = q;
     466          14 :         r->delim = d;
     467          14 :         r->extra_tsep = extra_tsep;
     468          14 :         r->has_header = has_header;
     469          14 :         f->sname = (char*)r; /* pass schema++ */
     470          14 :         return MAL_SUCCEED;
     471             : }
     472             : 
     473             : static void *
     474          12 : csv_load(void *BE, sql_subfunc *f, char *filename, sql_exp *topn)
     475             : {
     476          12 :         backend *be = (backend*)BE;
     477          12 :         mvc *sql = be->mvc;
     478          12 :         csv_t *r = (csv_t *)f->sname;
     479          12 :         sql_table *t = NULL;
     480             : 
     481          12 :         if (mvc_create_table( &t, be->mvc, be->mvc->session->tr->tmp/* misuse tmp schema */, f->tname /*gettable name*/, tt_table, false, SQL_DECLARED_TABLE, 0, 0, false) != LOG_OK)
     482             :                 //throw(SQL, SQLSTATE(42000), "csv" RUNTIME_FILE_NOT_FOUND);
     483             :                 /* alloc error */
     484             :                 return NULL;
     485             : 
     486          12 :         node *n, *nn = f->colnames->h, *tn = f->coltypes->h;
     487          48 :         for (n = f->res->h; n; n = n->next, nn = nn->next, tn = tn->next) {
     488          36 :                 const char *name = nn->data;
     489          36 :                 sql_subtype *tp = tn->data;
     490          36 :                 sql_column *c = NULL;
     491             : 
     492          36 :                 if (!tp || mvc_create_column(&c, be->mvc, t, name, tp) != LOG_OK) {
     493             :                         //throw(SQL, SQLSTATE(42000), "csv" RUNTIME_LOAD_ERROR);
     494           0 :                         return NULL;
     495             :                 }
     496             :         }
     497             :         /* (res bats) := import(table T, 'delimit', '\n', 'quote', str:nil, fname, lng:nil, 0/1, 0, str:nil, int:nil, * int:nil ); */
     498             : 
     499             :         /* lookup copy_from */
     500          12 :         sql_subfunc *cf = sql_find_func(sql, "sys", "copyfrom", 14, F_UNION, true, NULL);
     501          12 :         cf->res = f->res;
     502             : 
     503          12 :         sql_subtype tpe;
     504          12 :         sql_find_subtype(&tpe, "varchar", 0, 0);
     505          12 :         char tsep[2], rsep[3], ssep[2];
     506          12 :         tsep[0] = r->delim;
     507          12 :         tsep[1] = 0;
     508          12 :         ssep[0] = r->quote;
     509          12 :         ssep[1] = 0;
     510          12 :         if (r->extra_tsep) {
     511           0 :                 rsep[0] = r->delim;
     512           0 :                 rsep[1] = '\n';
     513           0 :                 rsep[2] = 0;
     514             :         } else {
     515          12 :                 rsep[0] = '\n';
     516          12 :                 rsep[1] = 0;
     517             :         }
     518          12 :         list *args = new_exp_list(sql->sa);
     519             : 
     520          12 :         append(args, exp_atom_ptr(sql->sa, t));
     521          12 :         append(args, exp_atom_str(sql->sa, tsep, &tpe));
     522          12 :         append(args, exp_atom_str(sql->sa, rsep, &tpe));
     523          12 :         append(args, exp_atom_str(sql->sa, ssep, &tpe));
     524             : 
     525          12 :         append(args, exp_atom_str(sql->sa, "", &tpe));
     526          12 :         append(args, exp_atom_str(sql->sa, filename, &tpe));
     527          12 :         append(args, topn ? topn: exp_atom_lng(sql->sa, -1));
     528          24 :         append(args, exp_atom_lng(sql->sa, r->has_header?2:1));
     529             : 
     530          12 :         append(args, exp_atom_int(sql->sa, 0));
     531          12 :         append(args, exp_atom_str(sql->sa, NULL, &tpe));
     532          12 :         append(args, exp_atom_int(sql->sa, 0));
     533          12 :         append(args, exp_atom_int(sql->sa, 0));
     534             : 
     535          12 :         append(args, exp_atom_str(sql->sa, ".", &tpe));
     536          12 :         append(args, exp_atom_str(sql->sa, NULL, &tpe));
     537             : 
     538          12 :         sql_exp *import = exp_op(sql->sa, args, cf);
     539             : 
     540          12 :         return exp_bin(be, import, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0);
     541             : }
     542             : 
     543             : static str
     544         315 : CSVprelude(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
     545             : {
     546         315 :         (void)cntxt; (void)mb; (void)stk; (void)pci;
     547             : 
     548         315 :         fl_register("csv", &csv_relation, &csv_load);
     549         315 :         fl_register("tsv", &csv_relation, &csv_load);
     550         315 :         fl_register("psv", &csv_relation, &csv_load);
     551         315 :         return MAL_SUCCEED;
     552             : }
     553             : 
     554             : static str
     555         314 : CSVepilogue(void *ret)
     556             : {
     557         314 :         fl_unregister("csv");
     558         314 :         fl_unregister("tsv");
     559         314 :         fl_unregister("psv");
     560         314 :         (void)ret;
     561         314 :         return MAL_SUCCEED;
     562             : }
     563             : 
     564             : #include "sql_scenario.h"
     565             : #include "mel.h"
     566             : 
     567             : static mel_func csv_init_funcs[] = {
     568             :         pattern("csv", "prelude", CSVprelude, false, "", noargs),
     569             :         command("csv", "epilogue", CSVepilogue, false, "", noargs),
     570             : { .imp=NULL }
     571             : };
     572             : 
     573             : #include "mal_import.h"
     574             : #ifdef _MSC_VER
     575             : #undef read
     576             : #pragma section(".CRT$XCU",read)
     577             : #endif
     578         315 : LIB_STARTUP_FUNC(init_csv_mal)
     579         315 : { mal_module("csv", NULL, csv_init_funcs); }

Generated by: LCOV version 1.14