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

Generated by: LCOV version 1.14