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

Generated by: LCOV version 1.14