LCOV - code coverage report
Current view: top level - sql/backends/monet5/vaults/odbc - odbc_loader.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 731 907 80.6 %
Date: 2025-03-24 21:28:01 Functions: 13 15 86.7 %

          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 "gdk.h"      // COLnew(), BUNappend()
      15             : #include "gdk_time.h" // date_create(), daytime_create(), timestamp_create()
      16             : #include "mal_exception.h"
      17             : #include "mal_builder.h"
      18             : #include "mal_client.h"
      19             : #include "mutils.h"   /* utf8toutf16(), utf16toutf8() */
      20             : #include "rel_proto_loader.h"
      21             : #include "rel_exp.h"
      22             : // #include "sql_decimal.h"   /* decimal_from_str() */
      23             : 
      24             : #ifdef _MSC_VER
      25             : #include <WTypes.h>
      26             : #endif
      27             : #include <stdint.h>
      28             : #include <ctype.h>
      29             : #include <wchar.h>
      30             : 
      31             : /**** Define the ODBC Version our ODBC application complies with ****/
      32             : #define ODBCVER 0x0352          /* Important: this must be defined before include of sql.h and sqlext.h */
      33             : #include <sql.h>
      34             : #include <sqlext.h>
      35             : 
      36             : 
      37             : #define ODBC_RELATION 1
      38             : #define ODBC_LOADER   2
      39             : 
      40             : #define QUERY_MAX_COLUMNS 4096
      41             : #define MAX_COL_NAME_LEN  1023
      42             : #define MAX_TBL_NAME_LEN  1023
      43             : 
      44             : #ifdef HAVE_HGE
      45             : #define MAX_PREC  38
      46             : #else
      47             : #define MAX_PREC  18
      48             : #endif
      49             : 
      50             : /* MonetDB ODBC Driver defines in ODBCGlobal.h  SQL_HUGEINT 0x4000 */
      51             : #define SQL_HUGEINT     0x4000
      52             : 
      53             : typedef struct {
      54             :         SQLSMALLINT dataType;           /* ODBC datatype */
      55             :         SQLULEN columnSize;             /* ODBC colsize, contains precision for decimals */
      56             :         SQLSMALLINT decimalDigits;      /* ODBC dec. digits, contains scale for decimals */
      57             :         int battype;                    /* MonetDB atom type, used to create the BAT */
      58             :         BAT * bat;                      /* MonetDB BAT */
      59             :         SQLSMALLINT targetType;         /* needed for SQLGetData */
      60             :         SQLPOINTER * targetValuePtr;    /* needed for SQLGetData */
      61             :         SQLLEN bufferLength;            /* needed for SQLGetData */
      62             : } rescol_t;
      63             : 
      64             : /* map ODBC SQL datatype to MonetDB SQL datatype */
      65             : static sql_subtype *
      66         192 : map_rescol_type(SQLSMALLINT dataType, SQLULEN columnSize, SQLSMALLINT decimalDigits, mvc * sql)
      67             : {
      68         192 :         char * typenm;
      69         192 :         unsigned int interval_type = 0;
      70             : 
      71         192 :         switch (dataType) {
      72          45 :         case SQL_CHAR:
      73             :         case SQL_VARCHAR:
      74             :         case SQL_LONGVARCHAR:
      75             :         case SQL_WCHAR:
      76             :         case SQL_WVARCHAR:
      77             :         case SQL_WLONGVARCHAR:
      78             :         default:        /* all other ODBC types are also mapped to varchar for now */
      79             :                 /* all ODBC char datatypes are mapped to varchar. char and clob are internally not used anymore */
      80          45 :                 if (columnSize > (SQLULEN) INT_MAX)
      81             :                         columnSize = INT_MAX;
      82          45 :                 return sql_bind_subtype(sql->sa, "varchar", (unsigned int) columnSize, 0);
      83             : 
      84           3 :         case SQL_BINARY:
      85             :         case SQL_VARBINARY:
      86             :         case SQL_LONGVARBINARY:
      87           3 :                 if (columnSize > (SQLULEN) INT_MAX)
      88             :                         columnSize = INT_MAX;
      89           3 :                 return sql_bind_subtype(sql->sa, "blob", (unsigned int) columnSize, 0);
      90             : 
      91           9 :         case SQL_DECIMAL:
      92             :         case SQL_NUMERIC:
      93             :         {
      94             :                 /* columnSize contains the defined number of digits, so precision. */
      95             :                 /* decimalDigits contains the scale (which can be negative). */
      96           9 :                 if (columnSize > MAX_PREC || abs(decimalDigits) > MAX_PREC) {
      97             :                         /* too large precision/scale, not supported by MonetDB. Map this column to a string */
      98           0 :                         if (columnSize > (SQLULEN) INT_MAX)
      99             :                                 columnSize = INT_MAX;
     100           0 :                         return sql_bind_subtype(sql->sa, "varchar", (unsigned int) columnSize +3, 0);
     101             :                 }
     102             : 
     103           9 :                 return sql_bind_subtype(sql->sa, "varchar", (unsigned int) columnSize +3, 0);
     104             : //              unsigned int prec = MAX(1, columnSize); /* precision must be >= 1 */
     105             : //              unsigned int scale = MAX(0, decimalDigits); /* negative scales are not supported by MonetDB */
     106             : //              if (prec < scale)
     107             : //                      prec = scale;   /* make precision large enough to contain all decimal digits */
     108             : //              return sql_bind_subtype(sql->sa, "decimal", prec, scale);
     109             :         }
     110           3 :         case SQL_GUID:
     111             :         {
     112             :                 /* represents a uuid of length 36, such as: dbe7343c-1f11-4fa9-a9c8-a31cd26f92fe */
     113           3 :                 sql_subtype * tp = sql_bind_subtype(sql->sa, "uuid", 0, 0);        // this fails to return a valid pointer
     114           3 :                 if (tp != NULL)
     115             :                         return tp;
     116             :                 // try a different way
     117           3 :                 sql_schema *syss = mvc_bind_schema(sql, "sys");
     118           3 :                 if (syss) {
     119           3 :                         tp = SA_ZNEW(sql->sa, sql_subtype);
     120           3 :                         if (tp != NULL) {
     121           3 :                                 tp->digits = tp->scale = 0;
     122           3 :                                 tp->type = schema_bind_type(sql, syss, "uuid");
     123           3 :                                 if (tp->type != NULL)
     124             :                                         return tp;
     125             :                         }
     126             :                 }
     127             :                 /* fall back to map it to a char(36) result column type */
     128           0 :                 return sql_bind_subtype(sql->sa, "char", (unsigned int) UUID_STRLEN, 0);
     129             :         }
     130             : 
     131             :         case SQL_BIT:
     132             :                 typenm = "boolean";
     133             :                 break;
     134             : 
     135           4 :         case SQL_TINYINT:
     136           4 :                 typenm = "tinyint";
     137           4 :                 break;
     138           4 :         case SQL_SMALLINT:
     139           4 :                 typenm = "smallint";
     140           4 :                 break;
     141          31 :         case SQL_INTEGER:
     142          31 :                 typenm = "int";
     143          31 :                 break;
     144           4 :         case SQL_BIGINT:
     145           4 :                 typenm = "bigint";
     146           4 :                 break;
     147             : #ifdef HAVE_HGE
     148           4 :         case SQL_HUGEINT:
     149           4 :                 typenm = "hugeint";
     150           4 :                 break;
     151             : #endif
     152             : 
     153             :         case SQL_REAL:
     154           4 :                 typenm = "real";
     155             :                 break;
     156             :         case SQL_DOUBLE:
     157           8 :                 typenm = "double";
     158             :                 break;
     159           0 :         case SQL_FLOAT:
     160             :                 /* the precision of SQL_FLOAT can be either 24 or 53:
     161             :                    if it is 24, the SQL_FLOAT data type is the same as SQL_REAL;
     162             :                    if it is 53, the SQL_FLOAT data type is the same as SQL_DOUBLE. */
     163           0 :                 typenm = (columnSize == 7) ? "real" : "double";
     164             :                 break;
     165             : 
     166           4 :         case SQL_TYPE_DATE:
     167           4 :                 typenm = "date";
     168           4 :                 break;
     169           4 :         case SQL_TYPE_TIME:
     170             :                 /* decimalDigits contains the precision of fractions of a second */
     171           4 :                 typenm = "time";
     172           4 :                 break;
     173           6 :         case SQL_DATETIME:
     174             :         case SQL_TYPE_TIMESTAMP:
     175             :                 /* decimalDigits contains the precision of fractions of a second */
     176           6 :                 typenm = "timestamp";
     177           6 :                 break;
     178             : 
     179           4 :         case SQL_INTERVAL_YEAR:
     180           4 :                 typenm = "month_interval";
     181           4 :                 interval_type = 1;
     182           4 :                 break;
     183           4 :         case SQL_INTERVAL_YEAR_TO_MONTH:
     184           4 :                 typenm = "month_interval";
     185           4 :                 interval_type = 2;
     186           4 :                 break;
     187           4 :         case SQL_INTERVAL_MONTH:
     188           4 :                 typenm = "month_interval";
     189           4 :                 interval_type = 3;
     190           4 :                 break;
     191           6 :         case SQL_INTERVAL_DAY:
     192           6 :                 typenm = "day_interval";
     193           6 :                 interval_type = 4;
     194           6 :                 break;
     195           4 :         case SQL_INTERVAL_HOUR:
     196           4 :                 typenm = "sec_interval";
     197           4 :                 interval_type = 8;
     198           4 :                 break;
     199           4 :         case SQL_INTERVAL_MINUTE:
     200           4 :                 typenm = "sec_interval";
     201           4 :                 interval_type = 11;
     202           4 :                 break;
     203           4 :         case SQL_INTERVAL_SECOND:
     204           4 :                 typenm = "sec_interval";
     205           4 :                 interval_type = 13;
     206           4 :                 break;
     207           4 :         case SQL_INTERVAL_DAY_TO_HOUR:
     208           4 :                 typenm = "sec_interval";
     209           4 :                 interval_type = 5;
     210           4 :                 break;
     211           4 :         case SQL_INTERVAL_DAY_TO_MINUTE:
     212           4 :                 typenm = "sec_interval";
     213           4 :                 interval_type = 6;
     214           4 :                 break;
     215           4 :         case SQL_INTERVAL_DAY_TO_SECOND:
     216           4 :                 typenm = "sec_interval";
     217           4 :                 interval_type = 7;
     218           4 :                 break;
     219           4 :         case SQL_INTERVAL_HOUR_TO_MINUTE:
     220           4 :                 typenm = "sec_interval";
     221           4 :                 interval_type = 9;
     222           4 :                 break;
     223           4 :         case SQL_INTERVAL_HOUR_TO_SECOND:
     224           4 :                 typenm = "sec_interval";
     225           4 :                 interval_type = 10;
     226           4 :                 break;
     227           4 :         case SQL_INTERVAL_MINUTE_TO_SECOND:
     228           4 :                 typenm = "sec_interval";
     229           4 :                 interval_type = 12;
     230           4 :                 break;
     231             :         }
     232         132 :         return sql_bind_subtype(sql->sa, typenm, interval_type, 0);
     233             : }
     234             : 
     235             : /* return name for ODBC SQL datatype */
     236             : static char *
     237           0 : nameofSQLtype(SQLSMALLINT dataType)
     238             : {
     239             :         /* https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/sql-data-types */
     240           0 :         switch (dataType) {
     241             :         case SQL_CHAR:          return "CHAR";
     242           0 :         case SQL_VARCHAR:       return "VARCHAR";
     243           0 :         case SQL_LONGVARCHAR:   return "LONG VARCHAR";
     244           0 :         case SQL_WCHAR:         return "WCHAR";
     245           0 :         case SQL_WVARCHAR:      return "WVARCHAR";
     246           0 :         case SQL_WLONGVARCHAR:  return "WLONGVARCHAR";
     247           0 :         case SQL_DECIMAL:       return "DECIMAL";
     248           0 :         case SQL_NUMERIC:       return "NUMERIC";
     249           0 :         case SQL_SMALLINT:      return "SMALLINT";
     250           0 :         case SQL_INTEGER:       return "INTEGER";
     251           0 :         case SQL_REAL:          return "REAL";
     252           0 :         case SQL_FLOAT:         return "FLOAT";
     253           0 :         case SQL_DOUBLE:        return "DOUBLE";
     254           0 :         case SQL_BIT:           return "BIT";
     255           0 :         case SQL_TINYINT:       return "TINYINT";
     256           0 :         case SQL_BIGINT:        return "BIGINT";
     257           0 :         case SQL_BINARY:        return "BINARY";
     258           0 :         case SQL_VARBINARY:     return "VARBINARY";
     259           0 :         case SQL_LONGVARBINARY: return "LONG VARBINARY";
     260           0 :         case SQL_DATETIME:      return "DATETIME";
     261           0 :         case SQL_TYPE_DATE:     return "DATE";
     262           0 :         case SQL_TYPE_TIME:     return "TIME";
     263           0 :         case SQL_TYPE_TIMESTAMP:        return "TIMESTAMP";
     264           0 :         case SQL_INTERVAL_MONTH:        return "INTERVAL MONTH";
     265           0 :         case SQL_INTERVAL_YEAR:         return "INTERVAL YEAR";
     266           0 :         case SQL_INTERVAL_YEAR_TO_MONTH: return "INTERVAL YEAR TO MONTH";
     267           0 :         case SQL_INTERVAL_DAY:          return "INTERVAL DAY";
     268           0 :         case SQL_INTERVAL_HOUR:         return "INTERVAL HOUR";
     269           0 :         case SQL_INTERVAL_MINUTE:       return "INTERVAL MINUTE";
     270           0 :         case SQL_INTERVAL_SECOND:       return "INTERVAL SECOND";
     271           0 :         case SQL_INTERVAL_DAY_TO_HOUR:  return "INTERVAL DAY TO HOUR";
     272           0 :         case SQL_INTERVAL_DAY_TO_MINUTE:        return "INTERVAL DAY TO MINUTE";
     273           0 :         case SQL_INTERVAL_DAY_TO_SECOND:        return "INTERVAL DAY TO SECOND";
     274           0 :         case SQL_INTERVAL_HOUR_TO_MINUTE:       return "INTERVAL HOUR TO MINUTE";
     275           0 :         case SQL_INTERVAL_HOUR_TO_SECOND:       return "INTERVAL HOUR TO SECOND";
     276           0 :         case SQL_INTERVAL_MINUTE_TO_SECOND:     return "INTERVAL MINUTE TO SECOND";
     277           0 :         case SQL_GUID:          return "GUID";
     278           0 :         case SQL_HUGEINT:       return "HUGEINT";
     279           0 :         default:                return "Driver specific type";
     280             :         }
     281             : }
     282             : 
     283             : /* name of ODBC SQLRETURN codes */
     284             : static char *
     285           0 : nameOfRetCode(SQLRETURN code)
     286             : {
     287           0 :         switch (code) {
     288             :         case SQL_SUCCESS:               return "SQL_SUCCESS";
     289           0 :         case SQL_SUCCESS_WITH_INFO:     return "SQL_SUCCESS_WITH_INFO";
     290           0 :         case SQL_ERROR:                 return "SQL_ERROR";
     291           0 :         case SQL_INVALID_HANDLE:        return "SQL_INVALID_HANDLE";
     292           0 :         case SQL_STILL_EXECUTING:       return "SQL_STILL_EXECUTING";
     293           0 :         case SQL_NEED_DATA:             return "SQL_NEED_DATA";
     294           0 :         case SQL_NO_DATA:               return "SQL_NO_DATA";
     295           0 :         default:                return "SQLRETURN ??";
     296             :         }
     297             : }
     298             : 
     299             : #ifdef HAVE_HGE
     300             : static hge
     301           3 : str_to_hge(const char *s) {
     302           3 :         char c;
     303           3 :         char sign = '+';
     304           3 :         int i = 0;
     305           3 :         hge ret = 0;
     306             : 
     307           3 :         if (!s)
     308             :                 return 0;
     309             : 
     310           3 :         c = s[i];
     311           3 :         if (c == '-' || c == '+') {
     312           1 :                 sign = c;
     313           1 :                 c = s[++i];
     314             :         }
     315          82 :         while (c) {
     316          79 :                 if (c >= '0' && c <= '9') {
     317          79 :                         ret *= 10;
     318          79 :                         ret += (int) c - '0';
     319             :                 }
     320          79 :                 c = s[++i];
     321             :         }
     322           3 :         if (sign == '-')
     323           1 :                 ret = -ret;
     324             :         return ret;
     325             : }
     326             : #endif
     327             : 
     328             : /* an ODBC function call returned an error, get the error msg from the ODBC driver */
     329             : static char *
     330           5 : getErrMsg(SQLSMALLINT handleType, SQLHANDLE handle) {
     331           5 :         SQLRETURN ret;
     332           5 :         SQLCHAR state[SQL_SQLSTATE_SIZE +1];
     333           5 :         SQLINTEGER errnr;
     334           5 :         SQLCHAR msg[SQL_MAX_MESSAGE_LENGTH] = { 0 };
     335           5 :         SQLSMALLINT msglen = SQL_MAX_MESSAGE_LENGTH -1;
     336             : 
     337           5 :         if (handle == SQL_NULL_HSTMT)
     338             :                 return NULL;
     339             : 
     340             :         // TODO use ODBC W function
     341           5 :         ret = SQLGetDiagRec(handleType, handle, 1, state, &errnr, msg, SQL_MAX_MESSAGE_LENGTH -1, &msglen);
     342           5 :         if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
     343           5 :                 const char format[] = "SQLSTATE %s, Error code %d, Message %s";
     344             :                 /* ignore msg when using MS Excel ODBC driver, which does not support setting connection timeout */
     345           5 :                 if ((strcmp("IM006", (char *)state) == 0)
     346           0 :                  && (strcmp("[Microsoft][ODBC Driver Manager] Driver's SQLSetConnectAttr failed", (char *)msg) == 0)) {
     347           5 :                         return NULL;
     348             :                 }
     349             : 
     350           5 :                 if (msglen <= 0) {
     351             :                         /* e.g SQL_NTS */
     352           0 :                         msglen = (SQLSMALLINT) strlen((char *)msg);
     353             :                 }
     354           5 :                 char * retmsg = (char *) GDKmalloc(sizeof(format) + SQL_SQLSTATE_SIZE + 10 + msglen);
     355           5 :                 if (retmsg != NULL) {
     356           5 :                         if (state[SQL_SQLSTATE_SIZE] != '\0')
     357           0 :                                 state[SQL_SQLSTATE_SIZE] = '\0';
     358           5 :                         sprintf(retmsg, format, (char *)state, errnr, (char *)msg);
     359           5 :                         return retmsg;
     360             :                 }
     361             :         }
     362             :         return NULL;
     363             : }
     364             : 
     365             : /* utility function to safely close all opened ODBC resources */
     366             : static void
     367         107 : odbc_cleanup(SQLHANDLE env, SQLHANDLE dbc, SQLHANDLE stmt) {
     368         107 :         SQLRETURN ret = SQL_SUCCESS;
     369             : 
     370         107 :         if (stmt != SQL_NULL_HSTMT) {
     371         103 :                 ret = SQLFreeStmt(stmt, SQL_CLOSE);
     372         103 :                 if (ret != SQL_INVALID_HANDLE)
     373         103 :                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
     374             :         }
     375         107 :         if (dbc != SQL_NULL_HDBC) {
     376         107 :                 ret = SQLDisconnect(dbc);
     377         107 :                 if (ret != SQL_INVALID_HANDLE)
     378         107 :                         SQLFreeHandle(SQL_HANDLE_DBC, dbc);
     379             :         }
     380         107 :         if (env != SQL_NULL_HENV) {
     381         107 :                 SQLFreeHandle(SQL_HANDLE_ENV, env);
     382             :         }
     383         107 : }
     384             : 
     385             : /* copied from monetdb5/modules/mal/tablet.c */
     386             : static BAT *
     387         192 : bat_create(int adt, BUN nr)
     388             : {
     389         192 :         BAT *b = COLnew(0, adt, nr, TRANSIENT);
     390             : 
     391             :         /* check for correct structures */
     392         192 :         if (b == NULL)
     393             :                 return NULL;
     394         192 :         if ((b = BATsetaccess(b, BAT_APPEND)) == NULL) {
     395             :                 return NULL;
     396             :         }
     397             : 
     398             :         /* disable all properties here */
     399         192 :         b->tsorted = false;
     400         192 :         b->trevsorted = false;
     401         192 :         b->tnosorted = 0;
     402         192 :         b->tnorevsorted = 0;
     403         192 :         b->tseqbase = oid_nil;
     404         192 :         b->tkey = false;
     405         192 :         b->tnokey[0] = 0;
     406         192 :         b->tnokey[1] = 0;
     407         192 :         return b;
     408             : }
     409             : 
     410             : /* convert interval.day_second.fraction values to millisec fractions as needed by MonetDB interval types.
     411             :  * we need the columns decimalDigits specification to adjust the fractions value to millisec.
     412             :  */
     413             : static SQLUINTEGER
     414          12 : fraction2msec(SQLUINTEGER fraction, SQLSMALLINT decimaldigits) {
     415          12 :         SQLUINTEGER msec = fraction;
     416          12 :         if (msec == 0)
     417             :                 return 0;
     418             : 
     419           8 :         switch (decimaldigits) {
     420           8 :                 case 6: msec = fraction / 1000; break;
     421             :                 case 3: msec = fraction; break;
     422           0 :                 case 0: msec = fraction * 1000; break;
     423           0 :                 case 1: msec = fraction * 100; break;
     424           0 :                 case 2: msec = fraction * 10; break;
     425           0 :                 case 4: msec = fraction / 10; break;
     426           0 :                 case 5: msec = fraction / 100; break;
     427           0 :                 case 7: msec = fraction / 10000; break;
     428           0 :                 case 8: msec = fraction / 100000; break;
     429           0 :                 case 9: msec = fraction / 1000000; break;
     430             :         }
     431             : 
     432             :         // millisec value should be no larger than 999
     433           8 :         while (msec > 999) {
     434           0 :                 msec = msec / 10;
     435             :         }
     436             :         return msec;
     437             : }
     438             : 
     439             : /*
     440             :  * odbc_query() contains the logic for both odbc_relation() and ODBCloader()
     441             :  * the caller argument is ODBC_RELATION when called from odbc_relation and ODBC_LOADER when called from ODBCloader
     442             :  */
     443             : static str
     444         118 : odbc_query(int caller, mvc *sql, sql_subfunc *f, char *url, list *res_exps, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
     445             : {
     446         118 :         if (sql == NULL)
     447             :                 return "Missing mvc value.";
     448         118 :         if (f == NULL)
     449             :                 return "Missing sql_subfunc value.";
     450             : 
     451             :         /* check received url and extract the ODBC connection string and the SQL query */
     452         118 :         if (!url || (url && strncasecmp("odbc:", url, 5) != 0))
     453             :                 return "Invalid URI. Must start with 'odbc:'.";
     454             : 
     455             :         // skip 'odbc:' prefix from url so we get a connection string including the query
     456         118 :         char * con_str = &url[5];
     457             :         /* the connection string must start with 'DSN=' or 'FILEDSN=' or 'DRIVER='
     458             :            else the ODBC driver manager can't load the ODBC driver */
     459         118 :         if (con_str
     460         118 :           && (strncmp("DSN=", con_str, 4) != 0)
     461          46 :           && (strncmp("DRIVER=", con_str, 7) != 0)
     462          10 :           && (strncmp("FILEDSN=", con_str, 8) != 0))
     463             :                 return "Invalid ODBC connection string. Must start with 'DSN=' or 'FILEDSN=' or 'DRIVER='.";
     464             : 
     465             :         // locate the 'QUERY=' part to extract the SQL query string to execute
     466         109 :         char * qry_str = strstr(con_str, "QUERY=");
     467         109 :         if (qry_str == NULL)
     468             :                 return "Incomplete ODBC URI string. Missing 'QUERY=' part to specify the SQL SELECT query to execute.";
     469             : 
     470         107 :         char * query = GDKstrdup(&qry_str[6]);      // we expect that QUERY= is at the end of the connection string
     471         107 :         if (query == NULL || *query == 0) {
     472           0 :                 GDKfree(query);
     473           0 :                 return "Incomplete ODBC URI string. Missing SQL SELECT query after 'QUERY='.";
     474             :         }
     475             : 
     476             :         // create a new ODBC connection string without the QUERY= part
     477         107 :         char * odbc_con_str = GDKstrndup(con_str, qry_str - con_str);
     478         107 :         if (odbc_con_str == NULL) {
     479           0 :                 GDKfree(query);
     480           0 :                 return "Missing ODBC connection string.";
     481             :         }
     482             : 
     483         107 :         TRC_INFO(LOADER, "\nExtracted ODBC connection string: %s\n  and SQL query: %s\n", odbc_con_str, query);
     484             : 
     485             :         /* now we can (try to) connect to the ODBC driver and execute the SQL query */
     486         107 :         SQLRETURN ret = SQL_INVALID_HANDLE;
     487         107 :         SQLHANDLE env = SQL_NULL_HENV;
     488         107 :         SQLHANDLE dbc = SQL_NULL_HDBC;
     489         107 :         SQLHANDLE stmt = SQL_NULL_HSTMT;
     490         107 :         char * errmsg = NULL;
     491             : 
     492         107 :         ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
     493         107 :         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     494           0 :                 errmsg = "Allocate ODBC ENV handle failed.";
     495           0 :                 goto finish;
     496             :         }
     497         107 :         ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) (uintptr_t) SQL_OV_ODBC3, 0);
     498         107 :         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     499           0 :                 errmsg = "SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION ODBC3) failed.";
     500           0 :                 goto finish;
     501             :         }
     502             : 
     503         107 :         ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
     504         107 :         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     505           0 :                 errmsg = "Allocate ODBC DBC handle failed.";
     506           0 :                 goto finish;
     507             :         }
     508             :         /* to avoid an endless blocking SQLDriverConnect() set a login timeout of 8s */
     509         107 :         ret = SQLSetConnectAttr(dbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) (uintptr_t) 8UL, 0);
     510         107 :         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     511           0 :                 errmsg = "SQLSetConnectAttr(SQL_ATTR_LOGIN_TIMEOUT 8 sec) failed.";
     512           0 :                 goto finish;
     513             :         }
     514             : 
     515         107 :         SQLSMALLINT len = 0;
     516         107 :         uint16_t * odbc_con_Wstr = utf8toutf16(odbc_con_str);
     517             : #define MAX_CONNECT_OUT_STR 2048
     518         107 :         if (odbc_con_Wstr != NULL) {
     519         107 :                 SQLWCHAR outstr[MAX_CONNECT_OUT_STR];
     520         107 :                 ret = SQLDriverConnectW(dbc, NULL, (SQLWCHAR *) odbc_con_Wstr, SQL_NTS, outstr, MAX_CONNECT_OUT_STR, &len, SQL_DRIVER_NOPROMPT);
     521             :                 /* we no longer need odbc_con_Wstr */
     522         107 :                 free(odbc_con_Wstr);
     523             :         } else {
     524           0 :                 SQLCHAR outstr[MAX_CONNECT_OUT_STR];
     525           0 :                 ret = SQLDriverConnect(dbc, NULL, (SQLCHAR *) odbc_con_str, SQL_NTS, outstr, MAX_CONNECT_OUT_STR, &len, SQL_DRIVER_NOPROMPT);
     526             :         }
     527         107 :         if (ret == SQL_SUCCESS_WITH_INFO && caller == ODBC_RELATION) {
     528             :                 /* show the info warning, but only once */
     529           0 :                 char * ODBCmsg = getErrMsg(SQL_HANDLE_DBC, dbc);
     530           0 :                 TRC_INFO(LOADER, "SQLDriverConnect(%s) returned %s ODBCmsg: %s\n", odbc_con_str, nameOfRetCode(ret), (ODBCmsg) ? ODBCmsg : "");
     531           0 :                 if (ODBCmsg)
     532           0 :                         GDKfree(ODBCmsg);
     533             :         } else {
     534         107 :                 TRC_DEBUG(LOADER, "SQLDriverConnect(%s) returned %s\n", odbc_con_str, nameOfRetCode(ret));
     535             :         }
     536         107 :         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     537           4 :                 errmsg = "SQLDriverConnect failed.";
     538           4 :                 goto finish;
     539             :         }
     540             :         /* we no longer need odbc_con_str */
     541         103 :         GDKfree(odbc_con_str);
     542         103 :         odbc_con_str = NULL;
     543             : 
     544         103 :         ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
     545         103 :         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     546           0 :                 errmsg = "Allocate ODBC STMT handle failed.";
     547           0 :                 goto finish;
     548             :         }
     549             : 
     550             : #ifdef HAVE_HGE
     551             :         {
     552         103 :                 char DBMSname[128];
     553         103 :                 ret = SQLGetInfo(dbc, SQL_DBMS_NAME, (SQLPOINTER) &DBMSname, 127, NULL);
     554         103 :                 if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
     555         103 :                         TRC_DEBUG(LOADER, "SQLGetInfo(dbc, SQL_DBMS_NAME) returned %s\n", DBMSname);
     556         103 :                         if (strcmp("MonetDB", DBMSname) == 0) {
     557             :                                 /* instruct the MonetDB ODBC driver to return SQL_HUGEINT as column datatype */
     558          70 :                                 ret = SQLGetTypeInfo(stmt, SQL_HUGEINT);
     559          70 :                                 TRC_DEBUG(LOADER, "SQLGetTypeInfo(stmt, SQL_HUGEINT) returned %s\n", nameOfRetCode(ret));
     560          70 :                                 if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
     561          70 :                                         ret = SQLCloseCursor(stmt);
     562             :                                 }
     563             :                         }
     564             :                 }
     565             :         }
     566             : #endif
     567             : 
     568         103 :         uint16_t * query_Wstr = utf8toutf16(query);
     569         103 :         if (query_Wstr != NULL) {
     570         103 :                 ret = SQLExecDirectW(stmt, (SQLWCHAR *) query_Wstr, SQL_NTS);
     571             :                 /* we no longer need query_Wstr */
     572         103 :                 free(query_Wstr);
     573             :         } else {
     574           0 :                 ret = SQLExecDirect(stmt, (SQLCHAR *) query, SQL_NTS);
     575             :         }
     576         103 :         if (ret == SQL_SUCCESS_WITH_INFO && caller == ODBC_RELATION) {
     577             :                 /* show the info warning, but only once */
     578           0 :                 char * ODBCmsg = getErrMsg(SQL_HANDLE_STMT, stmt);
     579           0 :                 TRC_INFO(LOADER, "SQLExecDirect(%s) returned %s ODBCmsg: %s\n", query, nameOfRetCode(ret), (ODBCmsg) ? ODBCmsg : "");
     580           0 :                 if (ODBCmsg)
     581           0 :                         GDKfree(ODBCmsg);
     582             :         } else {
     583         103 :                 TRC_DEBUG(LOADER, "SQLExecDirect(%s) returned %s\n", query, nameOfRetCode(ret));
     584             :         }
     585         103 :         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     586           1 :                 errmsg = "SQLExecDirect query failed.";
     587           1 :                 goto finish;
     588             :         }
     589             :         /* we no longer need query string */
     590         102 :         GDKfree(query);
     591         102 :         query = NULL;
     592             : 
     593         102 :         SQLSMALLINT nr_cols = 0;
     594         102 :         ret = SQLNumResultCols(stmt, &nr_cols);
     595         102 :         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     596           0 :                 errmsg = "SQLNumResultCols failed.";
     597           0 :                 goto finish;
     598             :         }
     599         102 :         if (nr_cols <= 0) {
     600           0 :                 errmsg = "ODBC query did not return a resultset.";
     601           0 :                 goto finish;
     602             :         }
     603         102 :         TRC_INFO(LOADER, "Query has %d result columns\n", nr_cols);
     604         102 :         if (nr_cols > QUERY_MAX_COLUMNS) {
     605             :                 /* limit the number of data columns, as we do not want to block or blow up the mserver */
     606           0 :                 nr_cols = QUERY_MAX_COLUMNS;
     607           0 :                 TRC_INFO(LOADER, "ODBC_loader limited Query result to first %d columns.\n", nr_cols);
     608             :         }
     609             : 
     610             :         /* when called from odbc_relation() */
     611         102 :         if (caller == ODBC_RELATION) {
     612          51 :                 char tname[MAX_TBL_NAME_LEN +1];
     613          51 :                 char cname[MAX_COL_NAME_LEN +1];
     614          51 :                 sql_alias * tblname = NULL;
     615          51 :                 char * colname;
     616          51 :                 SQLSMALLINT dataType = 0;
     617          51 :                 SQLULEN columnSize = 0;
     618          51 :                 SQLSMALLINT decimalDigits = 0;
     619          51 :                 sql_subtype * sql_mtype;
     620          51 :                 list * typelist = sa_list(sql->sa);
     621          51 :                 list * nameslist = sa_list(sql->sa);
     622         243 :                 for (SQLUSMALLINT col = 1; col <= (SQLUSMALLINT) nr_cols; col++) {
     623             :                         /* for each result column get name, datatype, size and decdigits */
     624             :                         // TODO use ODBC W function
     625         192 :                         ret = SQLDescribeCol(stmt, col, (SQLCHAR *) cname, (SQLSMALLINT) MAX_COL_NAME_LEN,
     626             :                                         NULL, &dataType, &columnSize, &decimalDigits, NULL);
     627         192 :                         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     628           0 :                                 errmsg = "SQLDescribeCol failed.";
     629           0 :                                 goto finish;
     630             :                         }
     631         192 :                         TRC_DEBUG(LOADER, "ResCol %u, name: %s, type %d (%s), size %u, decdigits %d\n",
     632             :                                         col, cname, dataType, nameofSQLtype(dataType), (unsigned int)columnSize, decimalDigits);
     633         192 :                         sql_mtype = map_rescol_type(dataType, columnSize, decimalDigits, sql);
     634         192 :                         if (sql_mtype == NULL)
     635           0 :                                 continue;       /* skip this column */
     636             : 
     637         192 :                         colname = sa_strdup(sql->sa, cname);
     638         192 :                         list_append(nameslist, colname);
     639         192 :                         list_append(typelist, sql_mtype);
     640             : 
     641         192 :                         if (res_exps) {
     642             :                                 /* also get the table name for this result column */
     643             :                                 // TODO use ODBC W function
     644         192 :                                 ret = SQLColAttribute(stmt, col, SQL_DESC_TABLE_NAME, (SQLPOINTER) tname, (SQLSMALLINT) MAX_TBL_NAME_LEN, NULL, NULL);
     645         192 :                                 if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     646           0 :                                         strcpy(tname, "");
     647             :                                 }
     648         192 :                                 tblname = a_create(sql->sa, tname);
     649         192 :                                 sql_exp *ne = exp_column(sql->sa, tblname, colname, sql_mtype, CARD_MULTI, 1, 0, 0);
     650         192 :                                 set_basecol(ne);
     651         192 :                                 ne->alias.label = -(sql->nid++);
     652         192 :                                 list_append(res_exps, ne);
     653             :                         }
     654             :                 }
     655             : 
     656          51 :                 f->tname = sa_strdup(sql->sa, tname);
     657          51 :                 f->colnames = nameslist;
     658          51 :                 f->coltypes = typelist;
     659          51 :                 f->res = typelist;
     660          51 :                 goto finish;
     661             :         }
     662             : 
     663             :         /* when called from ODBCloader() */
     664          51 :         if (caller == ODBC_LOADER) {
     665          51 :                 rescol_t * colmetadata = (rescol_t *) GDKzalloc(nr_cols * sizeof(rescol_t));
     666          51 :                 if (colmetadata == NULL) {
     667           0 :                         errmsg = "GDKzalloc colmetadata[nr_cols] failed.";
     668           0 :                         goto finish;
     669             :                 }
     670             : 
     671             :                 /* allocate buffers for each of the fixed size atom types. */
     672          51 :                 bit bit_val = 0;
     673          51 :                 bte bte_val = 0;
     674          51 :                 sht sht_val = 0;
     675          51 :                 int int_val = 0;
     676          51 :                 lng lng_val = 0;
     677             : #ifdef HAVE_HGE
     678          51 :                 hge hge_val = 0;        // for hugeint and decimals with precision > 18
     679             : #endif
     680          51 :                 flt flt_val = 0;
     681          51 :                 dbl dbl_val = 0;
     682          51 :                 DATE_STRUCT date_val;
     683          51 :                 TIME_STRUCT time_val;
     684          51 :                 TIMESTAMP_STRUCT ts_val;
     685          51 :                 SQL_INTERVAL_STRUCT itv_val;
     686          51 :                 SQLGUID guid_val;
     687          51 :                 union {
     688             :                         uuid uuid_val;
     689             :                         uint8_t u[UUID_SIZE];
     690             :                 } u_val;
     691             : 
     692          51 :                 bool hasStrCols = false;
     693          51 :                 SQLULEN largestStringSize = 0;
     694          51 :                 bool hasBlobCols = false;
     695          51 :                 SQLULEN largestBlobSize = 0;
     696             : 
     697             :                 /* make bats with right atom type */
     698         243 :                 for (SQLUSMALLINT col = 0; col < (SQLUSMALLINT) nr_cols; col++) {
     699         192 :                         char cname[MAX_COL_NAME_LEN +1];
     700         192 :                         SQLSMALLINT dataType = 0;
     701         192 :                         SQLULEN columnSize = 0;
     702         192 :                         SQLSMALLINT decimalDigits = 0;
     703         192 :                         int battype = TYPE_str;
     704         192 :                         BAT * b = NULL;
     705             : 
     706             :                         /* for each result column get SQL datatype, size and decdigits */
     707             :                         // TODO use ODBC W function
     708         192 :                         ret = SQLDescribeCol(stmt, col+1, (SQLCHAR *) cname, (SQLSMALLINT) MAX_COL_NAME_LEN,
     709             :                                         NULL, &dataType, &columnSize, &decimalDigits, NULL);
     710         192 :                         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     711           0 :                                 errmsg = "SQLDescribeCol failed.";
     712             :                                 /* cleanup already created bats */
     713           0 :                                 while (col > 0) {
     714           0 :                                         col--;
     715           0 :                                         BBPreclaim(colmetadata[col].bat);
     716             :                                 }
     717           0 :                                 GDKfree(colmetadata);
     718           0 :                                 goto finish;
     719             :                         }
     720         192 :                         TRC_DEBUG(LOADER, "DescCol %u, name: %s, type %d (%s), size %u, decdigits %d\n",
     721             :                                         col+1, cname, dataType, nameofSQLtype(dataType), (unsigned int)columnSize, decimalDigits);
     722             : 
     723         192 :                         colmetadata[col].dataType = dataType;
     724         192 :                         colmetadata[col].columnSize = columnSize;
     725         192 :                         colmetadata[col].decimalDigits = decimalDigits;
     726         192 :                         colmetadata[col].bufferLength = 0;
     727             : 
     728         192 :                         battype = getBatType(getArgType(mb, pci, col));
     729         192 :                         colmetadata[col].battype = battype;
     730         192 :                         if (battype == TYPE_str) {
     731          54 :                                 hasStrCols = true;
     732          54 :                                 if (dataType == SQL_DECIMAL || dataType == SQL_NUMERIC) {
     733             :                                         /* read it as string */
     734           9 :                                         if (columnSize < 38) {
     735           7 :                                                 columnSize = 38;
     736             :                                         }
     737             :                                         /* add 3 for: sign, possible leading 0 and decimal separator */
     738           9 :                                         columnSize += 3;
     739           9 :                                         colmetadata[col].columnSize = columnSize;
     740             :                                 }
     741          54 :                                 if (columnSize > largestStringSize) {
     742             :                                         largestStringSize = columnSize;
     743             :                                 }
     744             :                         } else
     745             : #ifdef HAVE_HGE
     746         138 :                         if (battype == TYPE_hge) {
     747           4 :                                 if (dataType == SQL_HUGEINT) {
     748             :                                         /* read it as string */
     749           4 :                                         hasStrCols = true;
     750           4 :                                         if (columnSize < 50) {
     751           4 :                                                 columnSize = 50;
     752           4 :                                                 colmetadata[col].columnSize = columnSize;
     753             :                                         }
     754           4 :                                         if (columnSize > largestStringSize) {
     755             :                                                 largestStringSize = columnSize;
     756             :                                         }
     757           4 :                                         colmetadata[col].bufferLength = largestStringSize;
     758             :                                 }
     759             :                         } else
     760             : #endif
     761         134 :                         if (battype == TYPE_blob) {
     762           3 :                                 hasBlobCols = true;
     763           3 :                                 if (columnSize > largestBlobSize) {
     764             :                                         largestBlobSize = columnSize;
     765             :                                 }
     766             :                         }
     767             : 
     768             :                         /* mapping based on https://learn.microsoft.com/en-us/sql/odbc/reference/appendixes/c-data-types */
     769         192 :                         switch(dataType) {
     770          45 :                                 case SQL_CHAR:
     771             :                                 case SQL_VARCHAR:
     772             :                                 case SQL_LONGVARCHAR:
     773             :                                 case SQL_WCHAR:
     774             :                                 case SQL_WVARCHAR:
     775             :                                 case SQL_WLONGVARCHAR:
     776             :                                 default:
     777          45 :                                         colmetadata[col].targetType = SQL_C_CHAR;       // TODO later: SQL_C_WCHAR
     778             :                                         // colmetadata[col].targetValuePtr = (SQLPOINTER *) str_val;  // will be done after allocation
     779          45 :                                         break;
     780           5 :                                 case SQL_BIT:
     781           5 :                                         colmetadata[col].targetType = SQL_C_BIT;
     782           5 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &bit_val;
     783           5 :                                         break;
     784           4 :                                 case SQL_TINYINT:
     785           4 :                                         colmetadata[col].targetType = SQL_C_STINYINT;
     786           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &bte_val;
     787           4 :                                         break;
     788           4 :                                 case SQL_SMALLINT:
     789           4 :                                         colmetadata[col].targetType = SQL_C_SSHORT;
     790           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &sht_val;
     791           4 :                                         break;
     792          31 :                                 case SQL_INTEGER:
     793          31 :                                         colmetadata[col].targetType = SQL_C_SLONG;
     794          31 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &int_val;
     795          31 :                                         break;
     796           4 :                                 case SQL_BIGINT:
     797           4 :                                         colmetadata[col].targetType = SQL_C_SBIGINT;
     798           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &lng_val;
     799           4 :                                         break;
     800           4 :                                 case SQL_HUGEINT:
     801             :                                         /* read huge int data as string data as there is no SQL_C_SHUGEINT */
     802           4 :                                         colmetadata[col].targetType = SQL_C_CHAR;
     803             :                                         // colmetadata[col].targetValuePtr = (SQLPOINTER *) str_val;  // will be done after allocation
     804           4 :                                         break;
     805           9 :                                 case SQL_DECIMAL:
     806             :                                 case SQL_NUMERIC:
     807             :                                         /* read decimal data always as string data and convert it to the right internal decimal format and bat type */
     808           9 :                                         colmetadata[col].targetType = SQL_C_CHAR;
     809             :                                         // colmetadata[col].targetValuePtr = (SQLPOINTER *) str_val;  // will be done after allocation
     810           9 :                                         break;
     811           4 :                                 case SQL_REAL:
     812           4 :                                         colmetadata[col].targetType = SQL_C_FLOAT;
     813           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &flt_val;
     814           4 :                                         break;
     815           0 :                                 case SQL_FLOAT:
     816             :                                         /* use same logic as used in map_rescol_type() for SQL_FLOAT and SQL_DECIMAL */
     817           0 :                                         if (colmetadata[col].battype == TYPE_flt) {
     818           0 :                                                 colmetadata[col].dataType = SQL_REAL;
     819           0 :                                                 colmetadata[col].targetType = SQL_C_FLOAT;
     820           0 :                                                 colmetadata[col].targetValuePtr = (SQLPOINTER *) &flt_val;
     821             :                                         } else {
     822           0 :                                                 colmetadata[col].dataType = SQL_DOUBLE;
     823           0 :                                                 colmetadata[col].targetType = SQL_C_DOUBLE;
     824           0 :                                                 colmetadata[col].targetValuePtr = (SQLPOINTER *) &dbl_val;
     825             :                                         }
     826             :                                         break;
     827           8 :                                 case SQL_DOUBLE:
     828           8 :                                         colmetadata[col].targetType = SQL_C_DOUBLE;
     829           8 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &dbl_val;
     830           8 :                                         break;
     831           4 :                                 case SQL_TYPE_DATE:
     832           4 :                                         colmetadata[col].targetType = SQL_C_TYPE_DATE;
     833           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &date_val;
     834           4 :                                         break;
     835           4 :                                 case SQL_TYPE_TIME:
     836           4 :                                         colmetadata[col].targetType = SQL_C_TYPE_TIME;
     837           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &time_val;
     838           4 :                                         break;
     839           6 :                                 case SQL_DATETIME:
     840             :                                 case SQL_TYPE_TIMESTAMP:
     841           6 :                                         colmetadata[col].targetType = SQL_C_TYPE_TIMESTAMP;
     842           6 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &ts_val;
     843           6 :                                         break;
     844           4 :                                 case SQL_INTERVAL_YEAR:
     845           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_YEAR;
     846           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     847           4 :                                         break;
     848           4 :                                 case SQL_INTERVAL_YEAR_TO_MONTH:
     849           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_YEAR_TO_MONTH;
     850           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     851           4 :                                         break;
     852           4 :                                 case SQL_INTERVAL_MONTH:
     853           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_MONTH;
     854           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     855           4 :                                         break;
     856           6 :                                 case SQL_INTERVAL_DAY:
     857           6 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_DAY;
     858           6 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     859           6 :                                         break;
     860           4 :                                 case SQL_INTERVAL_HOUR:
     861           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_HOUR;
     862           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     863           4 :                                         break;
     864           4 :                                 case SQL_INTERVAL_MINUTE:
     865           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_MINUTE;
     866           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     867           4 :                                         break;
     868           4 :                                 case SQL_INTERVAL_SECOND:
     869           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_SECOND;
     870           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     871           4 :                                         break;
     872           4 :                                 case SQL_INTERVAL_DAY_TO_HOUR:
     873           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_DAY_TO_HOUR;
     874           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     875           4 :                                         break;
     876           4 :                                 case SQL_INTERVAL_DAY_TO_MINUTE:
     877           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_DAY_TO_MINUTE;
     878           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     879           4 :                                         break;
     880           4 :                                 case SQL_INTERVAL_DAY_TO_SECOND:
     881           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_DAY_TO_SECOND;
     882           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     883           4 :                                         break;
     884           4 :                                 case SQL_INTERVAL_HOUR_TO_MINUTE:
     885           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_HOUR_TO_MINUTE;
     886           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     887           4 :                                         break;
     888           4 :                                 case SQL_INTERVAL_HOUR_TO_SECOND:
     889           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_HOUR_TO_SECOND;
     890           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     891           4 :                                         break;
     892           4 :                                 case SQL_INTERVAL_MINUTE_TO_SECOND:
     893           4 :                                         colmetadata[col].targetType = SQL_C_INTERVAL_MINUTE_TO_SECOND;
     894           4 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
     895           4 :                                         break;
     896           3 :                                 case SQL_GUID:
     897           3 :                                         colmetadata[col].targetType = SQL_C_GUID;
     898           3 :                                         colmetadata[col].targetValuePtr = (SQLPOINTER *) &guid_val;
     899           3 :                                         colmetadata[col].bufferLength = (SQLLEN) sizeof(SQLGUID);
     900           3 :                                         break;
     901           3 :                                 case SQL_BINARY:
     902             :                                 case SQL_VARBINARY:
     903             :                                 case SQL_LONGVARBINARY:
     904           3 :                                         colmetadata[col].targetType = SQL_C_BINARY;
     905             :                                         // colmetadata[col].targetValuePtr = (SQLPOINTER *) bin_data;  // will be done after allocation
     906           3 :                                         break;
     907             :                         }
     908             : 
     909         192 :                         TRC_INFO(LOADER, "ResCol %u, name: %s, type %d (%s), size %u, decdigits %d, battype %d\n",
     910             :                                         col+1, cname, dataType, nameofSQLtype(dataType), (unsigned int)columnSize, decimalDigits, battype);
     911             : 
     912         192 :                         TRC_DEBUG(LOADER, "Before create BAT %d type %d\n", col+1, battype);
     913         192 :                         b = bat_create(battype, 0);
     914         192 :                         if (b) {
     915         192 :                                 colmetadata[col].bat = b;
     916         192 :                                 TRC_DEBUG(LOADER, "Created BAT %d\n", col+1);
     917             :                         } else {
     918           0 :                                 errmsg = "Failed to create bat.";
     919             :                                 /* cleanup already created bats */
     920           0 :                                 while (col > 0) {
     921           0 :                                         col--;
     922           0 :                                         BBPreclaim(colmetadata[col].bat);
     923             :                                 }
     924           0 :                                 GDKfree(colmetadata);
     925           0 :                                 goto finish;
     926             :                         }
     927             :                 }
     928             : 
     929             :                 /* allocate large enough read buffers for storing string (and binary blob) data */
     930          51 :                 char * str_val = NULL;          // TODO: change to wchar
     931          51 :                 uint8_t * bin_data = NULL;
     932             : 
     933          51 :                 if (largestStringSize == 0 && hasStrCols)       // no valid string length, use 65535 (64kB) as default
     934             :                         largestStringSize = 65535;
     935          51 :                 else if (largestStringSize < 1023)   // for very large decimals read as strings
     936             :                         largestStringSize = 1023;
     937          51 :                 else if (largestStringSize > 16777215)       // string length very large, limit to 16MB for now
     938             :                         largestStringSize = 16777215;
     939          51 :                 str_val = (char *)GDKmalloc((largestStringSize +1) * sizeof(char));     // +1 for the eos char
     940          51 :                 if (!str_val) {
     941           0 :                         errmsg = "Failed to alloc memory for largest rescol string buffer.";
     942           0 :                         goto finish_fetch;
     943             :                 }
     944          51 :                 TRC_DEBUG(LOADER, "Allocated str_val buffer of size %zu\n", (largestStringSize +1) * sizeof(char));
     945             : 
     946          51 :                 if (hasBlobCols) {
     947           1 :                         if (largestBlobSize == 0)       // no valid blob/binary data size, assume 1048576 (1MB) as default
     948             :                                 largestBlobSize = 1048576;
     949           0 :                         if (largestBlobSize > 16777216) // blob length very large, limit to 16MB for now
     950             :                                 largestBlobSize = 16777216;
     951           1 :                         bin_data = (uint8_t *)GDKmalloc(largestBlobSize * sizeof(uint8_t));
     952           1 :                         if (!bin_data) {
     953           0 :                                 errmsg = "Failed to alloc memory for largest rescol binary data buffer.";
     954           0 :                                 goto finish_fetch;
     955             :                         }
     956           1 :                         TRC_DEBUG(LOADER, "Allocated bin_data buffer of size %zu\n", largestBlobSize * sizeof(uint8_t));
     957             :                 }
     958             : 
     959             :                 /* after allocation of var sized buffers, update targetValuePtr and bufferLength for those columns */
     960         243 :                 for (SQLUSMALLINT col = 0; col < (SQLUSMALLINT) nr_cols; col++) {
     961         192 :                         switch (colmetadata[col].targetType) {
     962          58 :                         case SQL_C_CHAR:
     963          58 :                                 colmetadata[col].targetValuePtr = (SQLPOINTER *) str_val;
     964          58 :                                 colmetadata[col].bufferLength = largestStringSize;
     965          58 :                                 break;
     966           3 :                         case SQL_C_BINARY:
     967           3 :                                 colmetadata[col].targetValuePtr = (SQLPOINTER *) bin_data;
     968           3 :                                 colmetadata[col].bufferLength = largestBlobSize;
     969           3 :                                 break;
     970             : //      TODO            case SQL_C_WCHAR:
     971             : //                              colmetadata[col].targetValuePtr = (SQLPOINTER *) Wstr_val;
     972             : //                              colmetadata[col].bufferLength = largestWStringSize;
     973             :                         }
     974             :                 }
     975             : 
     976          51 :                 gdk_return gdkret = GDK_SUCCEED;
     977          51 :                 unsigned long row = 0;
     978          51 :                 ret = SQLFetch(stmt);   // TODO optimisation: use SQLExtendedFetch() to pull data array wise and use BUNappendmulti()
     979       15747 :                 while (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
     980       15696 :                         row++;
     981       15696 :                         TRC_DEBUG(LOADER, "Fetched row %lu\n", row);
     982             : 
     983       82395 :                         for (SQLUSMALLINT col = 0; col < (SQLUSMALLINT) nr_cols; col++) {
     984       66699 :                                 SQLSMALLINT sqltype = colmetadata[col].dataType;
     985       66699 :                                 BAT * b = colmetadata[col].bat;
     986       66699 :                                 SQLSMALLINT targetType = colmetadata[col].targetType;
     987       66699 :                                 SQLPOINTER * targetValuePtr = colmetadata[col].targetValuePtr;
     988       66699 :                                 SQLLEN bufferLength = colmetadata[col].bufferLength;
     989       66699 :                                 SQLLEN strLen = 0;
     990             : 
     991       66699 :                                 TRC_DEBUG(LOADER, "Before SQLGetData(col %u C_type %d buflen %d\n", col+1, targetType, (int)bufferLength);
     992       66699 :                                 ret = SQLGetData(stmt, col+1, targetType, targetValuePtr, bufferLength, &strLen);
     993       66699 :                                 if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
     994           0 :                                         char * ODBCmsg = getErrMsg(SQL_HANDLE_STMT, stmt);
     995           0 :                                         TRC_DEBUG(LOADER, "Failed to get C_type %d data for col %u of row %lu. ODBCmsg: %s\n",
     996             :                                                         targetType, col+1, row, (ODBCmsg) ? ODBCmsg : "");
     997           0 :                                         if (ODBCmsg)
     998           0 :                                                 GDKfree(ODBCmsg);
     999             : 
    1000             :                                         /* as all bats need to be the same length, append NULL value */
    1001           0 :                                         if (BUNappend(b, ATOMnilptr(b->ttype), false) != GDK_SUCCEED)
    1002           0 :                                                 TRC_ERROR(LOADER, "BUNappend(b, ATOMnilptr(b->ttype), false) failed after SQLGetData failed\n");
    1003             :                                 } else {
    1004       66699 :                                         if (strLen == SQL_NULL_DATA) {
    1005        1378 :                                                 TRC_DEBUG(LOADER, "Data row %lu col %u: NULL\n", row, col+1);
    1006        1378 :                                                 if (BUNappend(b, ATOMnilptr(b->ttype), false) != GDK_SUCCEED)
    1007           0 :                                                         TRC_ERROR(LOADER, "BUNappend(b, ATOMnilptr(b->ttype), false) failed for setting SQL_NULL_DATA\n");
    1008             :                                         } else {
    1009       65321 :                                                 switch(sqltype) {
    1010        9246 :                                                         case SQL_CHAR:
    1011             :                                                         case SQL_VARCHAR:
    1012             :                                                         case SQL_LONGVARCHAR:
    1013             :                                                         case SQL_WCHAR:
    1014             :                                                         case SQL_WVARCHAR:
    1015             :                                                         case SQL_WLONGVARCHAR:
    1016             :                                                         default:
    1017        9246 :                                                                 if (strLen != SQL_NTS && strLen >= 0) {
    1018             :                                                                         /* make sure it is a Nul Terminated String */
    1019        9246 :                                                                         if ((SQLULEN) strLen < largestStringSize) {
    1020        9246 :                                                                                 if (str_val[strLen] != '\0')
    1021           0 :                                                                                         str_val[strLen] = '\0';
    1022             :                                                                         } else {
    1023           0 :                                                                                 if (str_val[largestStringSize] != '\0')
    1024           0 :                                                                                         str_val[largestStringSize] = '\0';
    1025             :                                                                         }
    1026             :                                                                 }
    1027        9246 :                                                                 TRC_DEBUG(LOADER, "Data row %lu col %u: %s\n", row, col+1, str_val);
    1028        9246 :                                                                 switch (colmetadata[col].battype) {
    1029        9243 :                                                                         case TYPE_str:
    1030        9243 :                                                                                 gdkret = BUNappend(b, (void *) str_val, false);
    1031        9243 :                                                                                 break;
    1032             : #ifdef HAVE_HGE
    1033           3 :                                                                         case TYPE_hge:
    1034             :                                                                                 /* HUGEINT values are read as string */
    1035           3 :                                                                                 hge_val = str_to_hge(str_val);
    1036           3 :                                                                                 gdkret = BUNappend(b, (void *) &hge_val, false);
    1037           3 :                                                                                 break;
    1038             : #endif
    1039           0 :                                                                         default:
    1040           0 :                                                                                 gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1041           0 :                                                                                 break;
    1042             :                                                                 }
    1043             :                                                                 break;
    1044           4 :                                                         case SQL_BIT:
    1045           4 :                                                                 if (colmetadata[col].battype == TYPE_bit) {
    1046           4 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %x\n", row, col+1, bit_val);
    1047           4 :                                                                         gdkret = BUNappend(b, (void *) &bit_val, false);
    1048             :                                                                 } else {
    1049           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1050             :                                                                 }
    1051             :                                                                 break;
    1052           3 :                                                         case SQL_TINYINT:
    1053           3 :                                                                 if (colmetadata[col].battype == TYPE_bte) {
    1054           3 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %hd\n", row, col+1, (sht) bte_val);
    1055           3 :                                                                         gdkret = BUNappend(b, (void *) &bte_val, false);
    1056             :                                                                 } else {
    1057           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1058             :                                                                 }
    1059             :                                                                 break;
    1060           3 :                                                         case SQL_SMALLINT:
    1061           3 :                                                                 if (colmetadata[col].battype == TYPE_sht) {
    1062           3 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %hd\n", row, col+1, sht_val);
    1063           3 :                                                                         gdkret = BUNappend(b, (void *) &sht_val, false);
    1064             :                                                                 } else {
    1065           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1066             :                                                                 }
    1067             :                                                                 break;
    1068       49404 :                                                         case SQL_INTEGER:
    1069       49404 :                                                                 if (colmetadata[col].battype == TYPE_int) {
    1070       49404 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %d\n", row, col+1, int_val);
    1071       49404 :                                                                         gdkret = BUNappend(b, (void *) &int_val, false);
    1072             :                                                                 } else {
    1073           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1074             :                                                                 }
    1075             :                                                                 break;
    1076           3 :                                                         case SQL_BIGINT:
    1077           3 :                                                                 if (colmetadata[col].battype == TYPE_lng) {
    1078           3 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %" PRId64 "\n", row, col+1, lng_val);
    1079           3 :                                                                         gdkret = BUNappend(b, (void *) &lng_val, false);
    1080             :                                                                 } else {
    1081           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1082             :                                                                 }
    1083             :                                                                 break;
    1084           9 :                                                         case SQL_DECIMAL:
    1085             :                                                         case SQL_NUMERIC:
    1086           9 :                                                                 if (colmetadata[col].battype == TYPE_str) {
    1087           9 :                                                                         if (strLen != SQL_NTS && strLen >= 0) {
    1088             :                                                                                 /* make sure it is a Nul Terminated String */
    1089           9 :                                                                                 if ((SQLULEN) strLen < largestStringSize) {
    1090           9 :                                                                                         if (str_val[strLen] != '\0')
    1091           0 :                                                                                                 str_val[strLen] = '\0';
    1092             :                                                                                 } else {
    1093           0 :                                                                                         if (str_val[largestStringSize] != '\0')
    1094           0 :                                                                                                 str_val[largestStringSize] = '\0';
    1095             :                                                                                 }
    1096             :                                                                         }
    1097           9 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %s\n", row, col+1, str_val);
    1098           9 :                                                                         gdkret = BUNappend(b, (void *) str_val, false);
    1099             :                                                                 } else {
    1100           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1101             :                                                                 }
    1102             :                                                                 // TODO add support for battypes: bte, sht, int, lng, hge
    1103             :                                                                 // this requires conversion of string to the target bat type, with the scale (decimalDigits) semantics.
    1104             :                                                                 break;
    1105           3 :                                                         case SQL_REAL:
    1106           3 :                                                                 if (colmetadata[col].battype == TYPE_flt) {
    1107           3 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %f\n", row, col+1, flt_val);
    1108           3 :                                                                         gdkret = BUNappend(b, (void *) &flt_val, false);
    1109             :                                                                 } else {
    1110           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1111             :                                                                 }
    1112             :                                                                 break;
    1113        6169 :                                                         case SQL_DOUBLE:
    1114        6169 :                                                                 if (colmetadata[col].battype == TYPE_dbl) {
    1115        6169 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %f\n", row, col+1, dbl_val);
    1116        6169 :                                                                         gdkret = BUNappend(b, (void *) &dbl_val, false);
    1117             :                                                                 } else {
    1118           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1119             :                                                                 }
    1120             :                                                                 break;
    1121           0 :                                                         case SQL_FLOAT:
    1122           0 :                                                                 if (colmetadata[col].battype == TYPE_flt) {
    1123           0 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %f\n", row, col+1, flt_val);
    1124           0 :                                                                         gdkret = BUNappend(b, (void *) &flt_val, false);
    1125             :                                                                 } else {
    1126           0 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %f\n", row, col+1, dbl_val);
    1127           0 :                                                                         gdkret = BUNappend(b, (void *) &dbl_val, false);
    1128             :                                                                 }
    1129             :                                                                 break;
    1130           3 :                                                         case SQL_TYPE_DATE:
    1131           3 :                                                                 if (colmetadata[col].battype == TYPE_date) {
    1132           3 :                                                                         date mdate_val = date_create(date_val.year, date_val.month, date_val.day);
    1133           3 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: date(%04d-%02u-%02u)\n",
    1134             :                                                                                 row, col+1, date_val.year, date_val.month, date_val.day);
    1135           3 :                                                                         gdkret = BUNappend(b, (void *) &mdate_val, false);
    1136             :                                                                 } else {
    1137           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1138             :                                                                 }
    1139             :                                                                 break;
    1140           3 :                                                         case SQL_TYPE_TIME:
    1141           3 :                                                                 if (colmetadata[col].battype == TYPE_daytime) {
    1142           3 :                                                                         daytime daytime_val = daytime_create(time_val.hour, time_val.minute, time_val.second, 0);
    1143           3 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: daytime(%02u:%02u:%02u)\n",
    1144             :                                                                                 row, col+1, time_val.hour, time_val.minute, time_val.second);
    1145           3 :                                                                         gdkret = BUNappend(b, (void *) &daytime_val, false);
    1146             :                                                                 } else {
    1147           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1148             :                                                                 }
    1149             :                                                                 break;
    1150         426 :                                                         case SQL_DATETIME:
    1151             :                                                         case SQL_TYPE_TIMESTAMP:
    1152         426 :                                                                 if (colmetadata[col].battype == TYPE_timestamp) {
    1153         426 :                                                                         date mdate_val = date_create(ts_val.year, ts_val.month, ts_val.day);
    1154         426 :                                                                         daytime daytime_val = daytime_create(ts_val.hour, ts_val.minute, ts_val.second, ts_val.fraction);
    1155         426 :                                                                         timestamp timestamp_val = timestamp_create(mdate_val, daytime_val);
    1156         426 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: timestamp(%04d-%02u-%02u %02u:%02u:%02u.%06u)\n", row, col+1,
    1157             :                                                                                         ts_val.year, ts_val.month, ts_val.day, ts_val.hour, ts_val.minute, ts_val.second, ts_val.fraction);
    1158         426 :                                                                         gdkret = BUNappend(b, (void *) &timestamp_val, false);
    1159             :                                                                 } else {
    1160           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1161             :                                                                 }
    1162             :                                                                 break;
    1163           9 :                                                         case SQL_INTERVAL_YEAR:
    1164             :                                                         case SQL_INTERVAL_YEAR_TO_MONTH:
    1165             :                                                         case SQL_INTERVAL_MONTH:
    1166           9 :                                                                 if (colmetadata[col].battype == TYPE_int) {
    1167           9 :                                                                         switch (itv_val.interval_type) {
    1168           3 :                                                                         case SQL_IS_YEAR:
    1169           3 :                                                                                 int_val = (int) itv_val.intval.year_month.year *12;
    1170           3 :                                                                                 break;
    1171           3 :                                                                         case SQL_IS_YEAR_TO_MONTH:
    1172           3 :                                                                                 int_val = (int) (itv_val.intval.year_month.year *12)
    1173           3 :                                                                                         + itv_val.intval.year_month.month;
    1174           3 :                                                                                 break;
    1175           3 :                                                                         case SQL_IS_MONTH:
    1176           3 :                                                                                 int_val = (int) itv_val.intval.year_month.month;
    1177           3 :                                                                                 break;
    1178           0 :                                                                         default:
    1179           0 :                                                                                 int_val = 0;
    1180             :                                                                         }
    1181             : 
    1182           9 :                                                                         if (itv_val.interval_sign == SQL_TRUE)
    1183           3 :                                                                                 int_val = -int_val;
    1184           9 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %d\n", row, col+1, int_val);
    1185           9 :                                                                         gdkret = BUNappend(b, (void *) &int_val, false);
    1186             :                                                                 } else {
    1187           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1188             :                                                                 }
    1189             :                                                                 break;
    1190          32 :                                                         case SQL_INTERVAL_DAY:
    1191             :                                                         case SQL_INTERVAL_HOUR:
    1192             :                                                         case SQL_INTERVAL_MINUTE:
    1193             :                                                         case SQL_INTERVAL_SECOND:
    1194             :                                                         case SQL_INTERVAL_DAY_TO_HOUR:
    1195             :                                                         case SQL_INTERVAL_DAY_TO_MINUTE:
    1196             :                                                         case SQL_INTERVAL_DAY_TO_SECOND:
    1197             :                                                         case SQL_INTERVAL_HOUR_TO_MINUTE:
    1198             :                                                         case SQL_INTERVAL_HOUR_TO_SECOND:
    1199             :                                                         case SQL_INTERVAL_MINUTE_TO_SECOND:
    1200          32 :                                                                 if (colmetadata[col].battype == TYPE_lng) {
    1201          32 :                                                                         switch (itv_val.interval_type) {
    1202           5 :                                                                         case SQL_IS_DAY:
    1203           5 :                                                                                 lng_val = (lng) itv_val.intval.day_second.day * (24*60*60*1000);
    1204           5 :                                                                                 break;
    1205           3 :                                                                         case SQL_IS_HOUR:
    1206           3 :                                                                                 lng_val = (lng) itv_val.intval.day_second.hour * (60*60*1000);
    1207           3 :                                                                                 break;
    1208           3 :                                                                         case SQL_IS_MINUTE:
    1209           3 :                                                                                 lng_val = (lng) itv_val.intval.day_second.minute * (60*1000);
    1210           3 :                                                                                 break;
    1211           3 :                                                                         case SQL_IS_SECOND:
    1212           6 :                                                                                 lng_val = (lng) (itv_val.intval.day_second.second * 1000)
    1213           3 :                                                                                         + fraction2msec(itv_val.intval.day_second.fraction, colmetadata[col].decimalDigits);
    1214           3 :                                                                                 break;
    1215           3 :                                                                         case SQL_IS_DAY_TO_HOUR:
    1216           3 :                                                                                 lng_val = (lng) ((itv_val.intval.day_second.day *24)
    1217           3 :                                                                                         + itv_val.intval.day_second.hour) * (60*60*1000);
    1218           3 :                                                                                 break;
    1219           3 :                                                                         case SQL_IS_DAY_TO_MINUTE:
    1220           3 :                                                                                 lng_val = (lng) ((((itv_val.intval.day_second.day *24)
    1221           3 :                                                                                         + itv_val.intval.day_second.hour) *60)
    1222           3 :                                                                                         + itv_val.intval.day_second.minute) * (60*1000);
    1223           3 :                                                                                 break;
    1224           3 :                                                                         case SQL_IS_DAY_TO_SECOND:
    1225           6 :                                                                                 lng_val = (lng) (((((((itv_val.intval.day_second.day *24)
    1226           3 :                                                                                         + itv_val.intval.day_second.hour) *60)
    1227           3 :                                                                                         + itv_val.intval.day_second.minute) *60)
    1228           3 :                                                                                         + itv_val.intval.day_second.second) *1000)
    1229           3 :                                                                                         + fraction2msec(itv_val.intval.day_second.fraction, colmetadata[col].decimalDigits);
    1230           3 :                                                                                 break;
    1231           3 :                                                                         case SQL_IS_HOUR_TO_MINUTE:
    1232           3 :                                                                                 lng_val = (lng) ((itv_val.intval.day_second.hour *60)
    1233           3 :                                                                                         + itv_val.intval.day_second.minute) * (60*1000);
    1234           3 :                                                                                 break;
    1235           3 :                                                                         case SQL_IS_HOUR_TO_SECOND:
    1236           6 :                                                                                 lng_val = (lng) (((((itv_val.intval.day_second.hour *60)
    1237           3 :                                                                                         + itv_val.intval.day_second.minute) *60)
    1238           3 :                                                                                         + itv_val.intval.day_second.second) *1000)
    1239           3 :                                                                                         + fraction2msec(itv_val.intval.day_second.fraction, colmetadata[col].decimalDigits);
    1240           3 :                                                                                 break;
    1241           3 :                                                                         case SQL_IS_MINUTE_TO_SECOND:
    1242           6 :                                                                                 lng_val = (lng) (((itv_val.intval.day_second.minute *60)
    1243           3 :                                                                                         + itv_val.intval.day_second.second) *1000)
    1244           3 :                                                                                         + fraction2msec(itv_val.intval.day_second.fraction, colmetadata[col].decimalDigits);
    1245           3 :                                                                                 break;
    1246           0 :                                                                         default:
    1247           0 :                                                                                 lng_val = 0;
    1248           0 :                                                                                 break;
    1249             :                                                                         }
    1250             : 
    1251          32 :                                                                         if (itv_val.interval_sign == SQL_TRUE)
    1252          11 :                                                                                 lng_val = -lng_val;
    1253          32 :                                                                         TRC_DEBUG(LOADER, "Data row %lu col %u: %" PRId64 "\n", row, col+1, lng_val);
    1254          32 :                                                                         gdkret = BUNappend(b, (void *) &lng_val, false);
    1255             :                                                                 } else {
    1256           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1257             :                                                                 }
    1258             :                                                                 break;
    1259           2 :                                                         case SQL_GUID:
    1260           2 :                                                                 TRC_DEBUG(LOADER, "Data row %lu col %u: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", row, col+1,
    1261             :                                                                                 guid_val.Data1, guid_val.Data2, guid_val.Data3, guid_val.Data4[0], guid_val.Data4[1], guid_val.Data4[2],
    1262             :                                                                                 guid_val.Data4[3], guid_val.Data4[4], guid_val.Data4[5], guid_val.Data4[6], guid_val.Data4[7]);
    1263           2 :                                                                 if (colmetadata[col].battype == TYPE_uuid) {
    1264           2 :                                                                         uint8_t u;
    1265             :                                                                         // uuid is 16 bytes, same as SQLGUID guid_val
    1266           2 :                                                                         memcpy((void *) &u_val.uuid_val, (void *) &guid_val, sizeof(uuid));
    1267             :                                                                         // guid_str: beefc4f7-0264-4735-9b7a-75fd371ef803
    1268             :                                                                         // becomes
    1269             :                                                                         // uuid_str: f7c4efbe-6402-3547-9b7a-75fd371ef803
    1270             :                                                                         // have to fix the swapped bytes
    1271           2 :                                                                         u = u_val.u[0];
    1272           2 :                                                                         u_val.u[0] = u_val.u[3];
    1273           2 :                                                                         u_val.u[3] = u;
    1274           2 :                                                                         u = u_val.u[1];
    1275           2 :                                                                         u_val.u[1] = u_val.u[2];
    1276           2 :                                                                         u_val.u[2] = u;
    1277           2 :                                                                         u = u_val.u[4];
    1278           2 :                                                                         u_val.u[4] = u_val.u[5];
    1279           2 :                                                                         u_val.u[5] = u;
    1280           2 :                                                                         u = u_val.u[6];
    1281           2 :                                                                         u_val.u[6] = u_val.u[7];
    1282           2 :                                                                         u_val.u[7] = u;
    1283           2 :                                                                         gdkret = BUNappend(b, (void *) &u_val.uuid_val, false);
    1284             :                                                                 } else {
    1285           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1286             :                                                                 }
    1287             :                                                                 break;
    1288           2 :                                                         case SQL_BINARY:
    1289             :                                                         case SQL_VARBINARY:
    1290             :                                                         case SQL_LONGVARBINARY:
    1291           2 :                                                                 TRC_DEBUG(LOADER, "Data row %lu col %u: binary data[%d]\n", row, col+1, (int) strLen);
    1292           2 :                                                                 if (colmetadata[col].battype == TYPE_blob && strLen > 0) {
    1293             :                                                                         // convert bin_data to blob struct.
    1294           2 :                                                                         size_t bin_size = (size_t)strLen;
    1295           2 :                                                                         if (bin_size > (size_t)largestBlobSize)
    1296             :                                                                                 /* the data has been truncated */
    1297             :                                                                                 bin_size = (size_t)largestBlobSize;
    1298           2 :                                                                         blob * blb = (blob *) GDKmalloc(blobsize(bin_size));
    1299           2 :                                                                         if (blb) {
    1300           2 :                                                                                 blb->nitems = bin_size;
    1301           2 :                                                                                 memcpy(blb->data, bin_data, bin_size);
    1302           2 :                                                                                 gdkret = BUNappend(b, (void *) blb, false);
    1303           2 :                                                                                 GDKfree(blb);
    1304             :                                                                         } else {
    1305           0 :                                                                                 gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1306             :                                                                         }
    1307             :                                                                 } else {
    1308           0 :                                                                         gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
    1309             :                                                                 }
    1310             :                                                                 break;
    1311             :                                                 }
    1312       65321 :                                                 if (gdkret != GDK_SUCCEED)
    1313           0 :                                                         TRC_ERROR(LOADER, "BUNappend(b, val, false) failed!\n");
    1314             :                                         }
    1315             :                                 }
    1316             :                         }
    1317       15696 :                         ret = SQLFetch(stmt);   // get data of next row
    1318             :                 }
    1319             :                 /* the last SQLFetch() will return SQL_NO_DATA at end, treat it as success */
    1320          51 :                 if (ret == SQL_NO_DATA)
    1321          51 :                         ret = SQL_SUCCESS;      // we retrieved all rows
    1322          51 :                 TRC_INFO(LOADER, "Fetched %lu rows\n", row);
    1323             : 
    1324          51 :   finish_fetch:
    1325          51 :                 if (str_val)
    1326          51 :                         GDKfree(str_val);
    1327          51 :                 if (bin_data)
    1328           1 :                         GDKfree(bin_data);
    1329             : 
    1330             :                 /* pass bats to caller */
    1331             :                 if (colmetadata) {
    1332         243 :                         for (int col = 0; col < (int) nr_cols; col++) {
    1333         192 :                                 bat * rescol = getArgReference_bat(stk, pci, col);
    1334         192 :                                 BAT * b = colmetadata[col].bat;
    1335         192 :                                 if (rescol && b) {
    1336         192 :                                         *rescol = b->batCacheid;
    1337         192 :                                         BBPkeepref(b);
    1338             :                                 }
    1339         192 :                                 TRC_DEBUG(LOADER, "col %d pass bat %d\n", col, b->ttype);
    1340             :                         }
    1341             :                         /* free locally allocated memory */
    1342          51 :                         GDKfree(colmetadata);
    1343             :                 }
    1344             :         } /* end of: if (caller == ODBC_LOADER) */
    1345             : 
    1346           0 :   finish:
    1347         107 :         if (query)
    1348           5 :                 GDKfree(query);
    1349         107 :         if (odbc_con_str)
    1350           4 :                 GDKfree(odbc_con_str);
    1351             : 
    1352         107 :         TRC_DEBUG(LOADER, "caller %d at finish, ret %d (%s) errmsg %s\n", caller, ret, nameOfRetCode(ret), (errmsg) ? errmsg : "");
    1353             : 
    1354         107 :         if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
    1355             :                 /* an ODBC function call returned an error or warning, get the error msg from the ODBC driver */
    1356           5 :                 SQLSMALLINT handleType;
    1357           5 :                 SQLHANDLE handle;
    1358           5 :                 str retmsg;
    1359           5 :                 char * ODBCmsg;
    1360             : 
    1361             :                 /* get err message(s) from the right handle */
    1362           5 :                 if (stmt != SQL_NULL_HSTMT) {
    1363             :                         handleType = SQL_HANDLE_STMT;
    1364             :                         handle = stmt;
    1365             :                 } else
    1366           4 :                 if (dbc != SQL_NULL_HDBC) {
    1367             :                         handleType = SQL_HANDLE_DBC;
    1368             :                         handle = dbc;
    1369             :                 } else {
    1370           0 :                         handleType = SQL_HANDLE_ENV;
    1371           0 :                         handle = env;
    1372             :                 }
    1373           5 :                 ODBCmsg = getErrMsg(handleType, handle);
    1374           5 :                 if (errmsg != NULL) {
    1375           5 :                         retmsg = sa_message(sql->sa, "odbc_loader" " %s %s", errmsg, (ODBCmsg) ? ODBCmsg : "");
    1376             :                 } else {
    1377           0 :                         retmsg = sa_message(sql->sa, "odbc_loader" " %s", (ODBCmsg) ? ODBCmsg : "");
    1378             :                 }
    1379           5 :                 if (ODBCmsg)
    1380           5 :                         GDKfree(ODBCmsg);
    1381           5 :                 odbc_cleanup(env, dbc, stmt);
    1382           5 :                 return retmsg;
    1383             :         }
    1384             : 
    1385         102 :         odbc_cleanup(env, dbc, stmt);
    1386         102 :         TRC_DEBUG(LOADER, "after odbc_cleanup(%p, %p, %p) errmsg %s\n", env, dbc, stmt, (errmsg) ? errmsg : "");
    1387             :         return (errmsg != NULL) ? (str)errmsg : MAL_SUCCEED;
    1388             : }
    1389             : 
    1390             : /*
    1391             :  * returns an error string (static or via tmp sa_allocator allocated), NULL on success
    1392             :  *
    1393             :  * Extend the subfunc f with result columns, ie.
    1394             :         f->res = typelist;
    1395             :         f->coltypes = typelist;
    1396             :         f->colnames = nameslist; use tname if passed, for the relation name
    1397             :  * Fill the list res_exps, with one result expressions per resulting column.
    1398             :  */
    1399             : static str
    1400          67 : odbc_relation(mvc *sql, sql_subfunc *f, char *url, list *res_exps, char *aname)
    1401             : {
    1402          67 :         (void) aname;
    1403          67 :         return odbc_query(ODBC_RELATION, sql, f, url, res_exps, NULL, NULL, NULL);
    1404             : }
    1405             : 
    1406             : static void *
    1407          51 : odbc_load(void *BE, sql_subfunc *f, char *url, sql_exp *topn)
    1408             : {
    1409          51 :         backend *be = (backend*)BE;
    1410          51 :         if (!f)
    1411             :                 return NULL;
    1412             : 
    1413          51 :         (void)topn;
    1414             : 
    1415          51 :         InstrPtr q = newStmtArgs(be->mb, "odbc", "loader", list_length(f->coltypes) + 2);
    1416          51 :         int col = 0;
    1417          51 :         list *l = sa_list(be->mvc->sa);
    1418         243 :         for (node *n = f->coltypes->h, *nn = f->colnames->h; n && nn; col++, n = n->next, nn = nn->next) {
    1419         192 :                 const char *name = nn->data;
    1420         192 :                 sql_subtype *tp = n->data;
    1421         192 :                 if (tp) {
    1422         192 :                         int type = newBatType(tp->type->localtype);
    1423         192 :                         if (col)
    1424         141 :                                 q = pushReturn(be->mb, q, newTmpVariable(be->mb, type));
    1425             :                         else
    1426          51 :                                 getArg(q, 0) = newTmpVariable(be->mb, type);
    1427         192 :                         stmt *s = stmt_blackbox_result(be, q, col, tp);
    1428         192 :                         sql_alias *ta = a_create(be->mvc->sa, f->tname);
    1429         192 :                         s = stmt_alias(be, s, col+1, ta, name);
    1430         192 :                         list_append(l, s);
    1431             :                 }
    1432             :         }
    1433          51 :         q = pushStr(be->mb, q, url);
    1434          51 :         q = pushPtr(be->mb, q, f);
    1435          51 :         pushInstruction(be->mb, q);
    1436          51 :         return stmt_list(be, l);
    1437             : }
    1438             : 
    1439             : static str
    1440          51 : ODBCloader(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
    1441             : {
    1442          51 :         backend *be = NULL;
    1443          51 :         str msg;
    1444          51 :         if ((msg = getBackendContext(cntxt, &be)) != NULL)
    1445             :                 return msg;
    1446          51 :         str uri = *getArgReference_str(stk, pci, pci->retc);
    1447          51 :         sql_subfunc *f = *(sql_subfunc**)getArgReference_ptr(stk, pci, pci->retc+1);
    1448             : 
    1449          51 :         return odbc_query(ODBC_LOADER, be->mvc, f, uri, NULL, mb, stk, pci);
    1450             :         //return MAL_SUCCEED;
    1451             : }
    1452             : 
    1453             : static str
    1454           1 : ODBCprelude(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
    1455             : {
    1456           1 :         (void)cntxt; (void)mb; (void)stk; (void)pci;
    1457           1 :         pl_register("odbc", &odbc_relation, &odbc_load);
    1458           1 :         return MAL_SUCCEED;
    1459             : }
    1460             : 
    1461             : static str
    1462           1 : ODBCepilogue(void *ret)
    1463             : {
    1464           1 :         (void)ret;
    1465           1 :         pl_unregister("odbc");
    1466           1 :         return MAL_SUCCEED;
    1467             : }
    1468             : 
    1469             : #include "sql_scenario.h"
    1470             : #include "mel.h"
    1471             : 
    1472             : static mel_func odbc_init_funcs[] = {
    1473             :         pattern("odbc", "prelude", ODBCprelude, false, "", noargs),
    1474             :         command("odbc", "epilogue", ODBCepilogue, false, "", noargs),
    1475             :         pattern("odbc", "loader", ODBCloader, true, "Import a query result via the odbc uri", args(1,3, batvarargany("",0),arg("uri",str),arg("func",ptr))),
    1476             : { .imp=NULL }
    1477             : };
    1478             : 
    1479             : #include "mal_import.h"
    1480             : #ifdef _MSC_VER
    1481             : #undef read
    1482             : #pragma section(".CRT$XCU",read)
    1483             : #endif
    1484           1 : LIB_STARTUP_FUNC(init_odbc_mal)
    1485           1 : { mal_module("odbc", NULL, odbc_init_funcs); }

Generated by: LCOV version 1.14