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 : char * tblname;
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 51 : strcpy(tname, "");
623 243 : for (SQLUSMALLINT col = 1; col <= (SQLUSMALLINT) nr_cols; col++) {
624 : /* for each result column get name, datatype, size and decdigits */
625 : // TODO use ODBC W function
626 192 : ret = SQLDescribeCol(stmt, col, (SQLCHAR *) cname, (SQLSMALLINT) MAX_COL_NAME_LEN,
627 : NULL, &dataType, &columnSize, &decimalDigits, NULL);
628 192 : if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
629 0 : errmsg = "SQLDescribeCol failed.";
630 0 : goto finish;
631 : }
632 192 : TRC_DEBUG(LOADER, "ResCol %u, name: %s, type %d (%s), size %u, decdigits %d\n",
633 : col, cname, dataType, nameofSQLtype(dataType), (unsigned int)columnSize, decimalDigits);
634 192 : sql_mtype = map_rescol_type(dataType, columnSize, decimalDigits, sql);
635 192 : if (sql_mtype == NULL)
636 0 : continue; /* skip this column */
637 :
638 192 : colname = sa_strdup(sql->sa, cname);
639 192 : list_append(nameslist, colname);
640 192 : list_append(typelist, sql_mtype);
641 :
642 192 : if (res_exps) {
643 : /* also get the table name for this result column */
644 : // TODO use ODBC W function
645 192 : ret = SQLColAttribute(stmt, col, SQL_DESC_TABLE_NAME, (SQLPOINTER) tname, (SQLSMALLINT) MAX_TBL_NAME_LEN, NULL, NULL);
646 192 : if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
647 0 : strcpy(tname, "");
648 : }
649 192 : tblname = sa_strdup(sql->sa, tname);
650 192 : sql_exp *ne = exp_column(sql->sa, tblname, colname, sql_mtype, CARD_MULTI, 1, 0, 0);
651 192 : set_basecol(ne);
652 192 : ne->alias.label = -(sql->nid++);
653 192 : list_append(res_exps, ne);
654 : }
655 : }
656 :
657 51 : f->tname = sa_strdup(sql->sa, tname);
658 51 : f->colnames = nameslist;
659 51 : f->coltypes = typelist;
660 51 : f->res = typelist;
661 51 : goto finish;
662 : }
663 :
664 : /* when called from ODBCloader() */
665 51 : if (caller == ODBC_LOADER) {
666 51 : rescol_t * colmetadata = (rescol_t *) GDKzalloc(nr_cols * sizeof(rescol_t));
667 51 : if (colmetadata == NULL) {
668 0 : errmsg = "GDKzalloc colmetadata[nr_cols] failed.";
669 0 : goto finish;
670 : }
671 :
672 : /* allocate buffers for each of the fixed size atom types. */
673 51 : bit bit_val = 0;
674 51 : bte bte_val = 0;
675 51 : sht sht_val = 0;
676 51 : int int_val = 0;
677 51 : lng lng_val = 0;
678 : #ifdef HAVE_HGE
679 51 : hge hge_val = 0; // for hugeint and decimals with precision > 18
680 : #endif
681 51 : flt flt_val = 0;
682 51 : dbl dbl_val = 0;
683 51 : DATE_STRUCT date_val;
684 51 : TIME_STRUCT time_val;
685 51 : TIMESTAMP_STRUCT ts_val;
686 51 : SQL_INTERVAL_STRUCT itv_val;
687 51 : SQLGUID guid_val;
688 51 : union {
689 : uuid uuid_val;
690 : uint8_t u[UUID_SIZE];
691 : } u_val;
692 :
693 51 : bool hasStrCols = false;
694 51 : SQLULEN largestStringSize = 0;
695 51 : bool hasBlobCols = false;
696 51 : SQLULEN largestBlobSize = 0;
697 :
698 : /* make bats with right atom type */
699 243 : for (SQLUSMALLINT col = 0; col < (SQLUSMALLINT) nr_cols; col++) {
700 192 : char cname[MAX_COL_NAME_LEN +1];
701 192 : SQLSMALLINT dataType = 0;
702 192 : SQLULEN columnSize = 0;
703 192 : SQLSMALLINT decimalDigits = 0;
704 192 : int battype = TYPE_str;
705 192 : BAT * b = NULL;
706 :
707 : /* for each result column get SQL datatype, size and decdigits */
708 : // TODO use ODBC W function
709 192 : ret = SQLDescribeCol(stmt, col+1, (SQLCHAR *) cname, (SQLSMALLINT) MAX_COL_NAME_LEN,
710 : NULL, &dataType, &columnSize, &decimalDigits, NULL);
711 192 : if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
712 0 : errmsg = "SQLDescribeCol failed.";
713 : /* cleanup already created bats */
714 0 : while (col > 0) {
715 0 : col--;
716 0 : BBPreclaim(colmetadata[col].bat);
717 : }
718 0 : GDKfree(colmetadata);
719 0 : goto finish;
720 : }
721 192 : TRC_DEBUG(LOADER, "DescCol %u, name: %s, type %d (%s), size %u, decdigits %d\n",
722 : col+1, cname, dataType, nameofSQLtype(dataType), (unsigned int)columnSize, decimalDigits);
723 :
724 192 : colmetadata[col].dataType = dataType;
725 192 : colmetadata[col].columnSize = columnSize;
726 192 : colmetadata[col].decimalDigits = decimalDigits;
727 192 : colmetadata[col].bufferLength = 0;
728 :
729 192 : battype = getBatType(getArgType(mb, pci, col));
730 192 : colmetadata[col].battype = battype;
731 192 : if (battype == TYPE_str) {
732 54 : hasStrCols = true;
733 54 : if (dataType == SQL_DECIMAL || dataType == SQL_NUMERIC) {
734 : /* read it as string */
735 9 : if (columnSize < 38) {
736 7 : columnSize = 38;
737 : }
738 : /* add 3 for: sign, possible leading 0 and decimal separator */
739 9 : columnSize += 3;
740 9 : colmetadata[col].columnSize = columnSize;
741 : }
742 54 : if (columnSize > largestStringSize) {
743 : largestStringSize = columnSize;
744 : }
745 : } else
746 : #ifdef HAVE_HGE
747 138 : if (battype == TYPE_hge) {
748 4 : if (dataType == SQL_HUGEINT) {
749 : /* read it as string */
750 4 : hasStrCols = true;
751 4 : if (columnSize < 50) {
752 4 : columnSize = 50;
753 4 : colmetadata[col].columnSize = columnSize;
754 : }
755 4 : if (columnSize > largestStringSize) {
756 : largestStringSize = columnSize;
757 : }
758 4 : colmetadata[col].bufferLength = largestStringSize;
759 : }
760 : } else
761 : #endif
762 134 : if (battype == TYPE_blob) {
763 3 : hasBlobCols = true;
764 3 : if (columnSize > largestBlobSize) {
765 : largestBlobSize = columnSize;
766 : }
767 : }
768 :
769 : /* mapping based on https://learn.microsoft.com/en-us/sql/odbc/reference/appendixes/c-data-types */
770 192 : switch(dataType) {
771 45 : case SQL_CHAR:
772 : case SQL_VARCHAR:
773 : case SQL_LONGVARCHAR:
774 : case SQL_WCHAR:
775 : case SQL_WVARCHAR:
776 : case SQL_WLONGVARCHAR:
777 : default:
778 45 : colmetadata[col].targetType = SQL_C_CHAR; // TODO later: SQL_C_WCHAR
779 : // colmetadata[col].targetValuePtr = (SQLPOINTER *) str_val; // will be done after allocation
780 45 : break;
781 5 : case SQL_BIT:
782 5 : colmetadata[col].targetType = SQL_C_BIT;
783 5 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &bit_val;
784 5 : break;
785 4 : case SQL_TINYINT:
786 4 : colmetadata[col].targetType = SQL_C_STINYINT;
787 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &bte_val;
788 4 : break;
789 4 : case SQL_SMALLINT:
790 4 : colmetadata[col].targetType = SQL_C_SSHORT;
791 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &sht_val;
792 4 : break;
793 31 : case SQL_INTEGER:
794 31 : colmetadata[col].targetType = SQL_C_SLONG;
795 31 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &int_val;
796 31 : break;
797 4 : case SQL_BIGINT:
798 4 : colmetadata[col].targetType = SQL_C_SBIGINT;
799 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &lng_val;
800 4 : break;
801 4 : case SQL_HUGEINT:
802 : /* read huge int data as string data as there is no SQL_C_SHUGEINT */
803 4 : colmetadata[col].targetType = SQL_C_CHAR;
804 : // colmetadata[col].targetValuePtr = (SQLPOINTER *) str_val; // will be done after allocation
805 4 : break;
806 9 : case SQL_DECIMAL:
807 : case SQL_NUMERIC:
808 : /* read decimal data always as string data and convert it to the right internal decimal format and bat type */
809 9 : colmetadata[col].targetType = SQL_C_CHAR;
810 : // colmetadata[col].targetValuePtr = (SQLPOINTER *) str_val; // will be done after allocation
811 9 : break;
812 4 : case SQL_REAL:
813 4 : colmetadata[col].targetType = SQL_C_FLOAT;
814 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &flt_val;
815 4 : break;
816 0 : case SQL_FLOAT:
817 : /* use same logic as used in map_rescol_type() for SQL_FLOAT and SQL_DECIMAL */
818 0 : if (colmetadata[col].battype == TYPE_flt) {
819 0 : colmetadata[col].dataType = SQL_REAL;
820 0 : colmetadata[col].targetType = SQL_C_FLOAT;
821 0 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &flt_val;
822 : } else {
823 0 : colmetadata[col].dataType = SQL_DOUBLE;
824 0 : colmetadata[col].targetType = SQL_C_DOUBLE;
825 0 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &dbl_val;
826 : }
827 : break;
828 8 : case SQL_DOUBLE:
829 8 : colmetadata[col].targetType = SQL_C_DOUBLE;
830 8 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &dbl_val;
831 8 : break;
832 4 : case SQL_TYPE_DATE:
833 4 : colmetadata[col].targetType = SQL_C_TYPE_DATE;
834 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &date_val;
835 4 : break;
836 4 : case SQL_TYPE_TIME:
837 4 : colmetadata[col].targetType = SQL_C_TYPE_TIME;
838 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &time_val;
839 4 : break;
840 6 : case SQL_DATETIME:
841 : case SQL_TYPE_TIMESTAMP:
842 6 : colmetadata[col].targetType = SQL_C_TYPE_TIMESTAMP;
843 6 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &ts_val;
844 6 : break;
845 4 : case SQL_INTERVAL_YEAR:
846 4 : colmetadata[col].targetType = SQL_C_INTERVAL_YEAR;
847 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
848 4 : break;
849 4 : case SQL_INTERVAL_YEAR_TO_MONTH:
850 4 : colmetadata[col].targetType = SQL_C_INTERVAL_YEAR_TO_MONTH;
851 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
852 4 : break;
853 4 : case SQL_INTERVAL_MONTH:
854 4 : colmetadata[col].targetType = SQL_C_INTERVAL_MONTH;
855 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
856 4 : break;
857 6 : case SQL_INTERVAL_DAY:
858 6 : colmetadata[col].targetType = SQL_C_INTERVAL_DAY;
859 6 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
860 6 : break;
861 4 : case SQL_INTERVAL_HOUR:
862 4 : colmetadata[col].targetType = SQL_C_INTERVAL_HOUR;
863 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
864 4 : break;
865 4 : case SQL_INTERVAL_MINUTE:
866 4 : colmetadata[col].targetType = SQL_C_INTERVAL_MINUTE;
867 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
868 4 : break;
869 4 : case SQL_INTERVAL_SECOND:
870 4 : colmetadata[col].targetType = SQL_C_INTERVAL_SECOND;
871 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
872 4 : break;
873 4 : case SQL_INTERVAL_DAY_TO_HOUR:
874 4 : colmetadata[col].targetType = SQL_C_INTERVAL_DAY_TO_HOUR;
875 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
876 4 : break;
877 4 : case SQL_INTERVAL_DAY_TO_MINUTE:
878 4 : colmetadata[col].targetType = SQL_C_INTERVAL_DAY_TO_MINUTE;
879 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
880 4 : break;
881 4 : case SQL_INTERVAL_DAY_TO_SECOND:
882 4 : colmetadata[col].targetType = SQL_C_INTERVAL_DAY_TO_SECOND;
883 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
884 4 : break;
885 4 : case SQL_INTERVAL_HOUR_TO_MINUTE:
886 4 : colmetadata[col].targetType = SQL_C_INTERVAL_HOUR_TO_MINUTE;
887 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
888 4 : break;
889 4 : case SQL_INTERVAL_HOUR_TO_SECOND:
890 4 : colmetadata[col].targetType = SQL_C_INTERVAL_HOUR_TO_SECOND;
891 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
892 4 : break;
893 4 : case SQL_INTERVAL_MINUTE_TO_SECOND:
894 4 : colmetadata[col].targetType = SQL_C_INTERVAL_MINUTE_TO_SECOND;
895 4 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &itv_val;
896 4 : break;
897 3 : case SQL_GUID:
898 3 : colmetadata[col].targetType = SQL_C_GUID;
899 3 : colmetadata[col].targetValuePtr = (SQLPOINTER *) &guid_val;
900 3 : colmetadata[col].bufferLength = (SQLLEN) sizeof(SQLGUID);
901 3 : break;
902 3 : case SQL_BINARY:
903 : case SQL_VARBINARY:
904 : case SQL_LONGVARBINARY:
905 3 : colmetadata[col].targetType = SQL_C_BINARY;
906 : // colmetadata[col].targetValuePtr = (SQLPOINTER *) bin_data; // will be done after allocation
907 3 : break;
908 : }
909 :
910 192 : TRC_INFO(LOADER, "ResCol %u, name: %s, type %d (%s), size %u, decdigits %d, battype %d\n",
911 : col+1, cname, dataType, nameofSQLtype(dataType), (unsigned int)columnSize, decimalDigits, battype);
912 :
913 192 : TRC_DEBUG(LOADER, "Before create BAT %d type %d\n", col+1, battype);
914 192 : b = bat_create(battype, 0);
915 192 : if (b) {
916 192 : colmetadata[col].bat = b;
917 192 : TRC_DEBUG(LOADER, "Created BAT %d\n", col+1);
918 : } else {
919 0 : errmsg = "Failed to create bat.";
920 : /* cleanup already created bats */
921 0 : while (col > 0) {
922 0 : col--;
923 0 : BBPreclaim(colmetadata[col].bat);
924 : }
925 0 : GDKfree(colmetadata);
926 0 : goto finish;
927 : }
928 : }
929 :
930 : /* allocate large enough read buffers for storing string (and binary blob) data */
931 51 : char * str_val = NULL; // TODO: change to wchar
932 51 : uint8_t * bin_data = NULL;
933 :
934 51 : if (largestStringSize == 0 && hasStrCols) // no valid string length, use 65535 (64kB) as default
935 : largestStringSize = 65535;
936 51 : else if (largestStringSize < 1023) // for very large decimals read as strings
937 : largestStringSize = 1023;
938 51 : else if (largestStringSize > 16777215) // string length very large, limit to 16MB for now
939 : largestStringSize = 16777215;
940 51 : str_val = (char *)GDKmalloc((largestStringSize +1) * sizeof(char)); // +1 for the eos char
941 51 : if (!str_val) {
942 0 : errmsg = "Failed to alloc memory for largest rescol string buffer.";
943 0 : goto finish_fetch;
944 : }
945 51 : TRC_DEBUG(LOADER, "Allocated str_val buffer of size %zu\n", (largestStringSize +1) * sizeof(char));
946 :
947 51 : if (hasBlobCols) {
948 1 : if (largestBlobSize == 0) // no valid blob/binary data size, assume 1048576 (1MB) as default
949 : largestBlobSize = 1048576;
950 0 : if (largestBlobSize > 16777216) // blob length very large, limit to 16MB for now
951 : largestBlobSize = 16777216;
952 1 : bin_data = (uint8_t *)GDKmalloc(largestBlobSize * sizeof(uint8_t));
953 1 : if (!bin_data) {
954 0 : errmsg = "Failed to alloc memory for largest rescol binary data buffer.";
955 0 : goto finish_fetch;
956 : }
957 1 : TRC_DEBUG(LOADER, "Allocated bin_data buffer of size %zu\n", largestBlobSize * sizeof(uint8_t));
958 : }
959 :
960 : /* after allocation of var sized buffers, update targetValuePtr and bufferLength for those columns */
961 243 : for (SQLUSMALLINT col = 0; col < (SQLUSMALLINT) nr_cols; col++) {
962 192 : switch (colmetadata[col].targetType) {
963 58 : case SQL_C_CHAR:
964 58 : colmetadata[col].targetValuePtr = (SQLPOINTER *) str_val;
965 58 : colmetadata[col].bufferLength = largestStringSize;
966 58 : break;
967 3 : case SQL_C_BINARY:
968 3 : colmetadata[col].targetValuePtr = (SQLPOINTER *) bin_data;
969 3 : colmetadata[col].bufferLength = largestBlobSize;
970 3 : break;
971 : // TODO case SQL_C_WCHAR:
972 : // colmetadata[col].targetValuePtr = (SQLPOINTER *) Wstr_val;
973 : // colmetadata[col].bufferLength = largestWStringSize;
974 : }
975 : }
976 :
977 51 : gdk_return gdkret = GDK_SUCCEED;
978 51 : unsigned long row = 0;
979 51 : ret = SQLFetch(stmt); // TODO optimisation: use SQLExtendedFetch() to pull data array wise and use BUNappendmulti()
980 15747 : while (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
981 15696 : row++;
982 15696 : TRC_DEBUG(LOADER, "Fetched row %lu\n", row);
983 :
984 82395 : for (SQLUSMALLINT col = 0; col < (SQLUSMALLINT) nr_cols; col++) {
985 66699 : SQLSMALLINT sqltype = colmetadata[col].dataType;
986 66699 : BAT * b = colmetadata[col].bat;
987 66699 : SQLSMALLINT targetType = colmetadata[col].targetType;
988 66699 : SQLPOINTER * targetValuePtr = colmetadata[col].targetValuePtr;
989 66699 : SQLLEN bufferLength = colmetadata[col].bufferLength;
990 66699 : SQLLEN strLen = 0;
991 :
992 66699 : TRC_DEBUG(LOADER, "Before SQLGetData(col %u C_type %d buflen %d\n", col+1, targetType, (int)bufferLength);
993 66699 : ret = SQLGetData(stmt, col+1, targetType, targetValuePtr, bufferLength, &strLen);
994 66699 : if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
995 0 : char * ODBCmsg = getErrMsg(SQL_HANDLE_STMT, stmt);
996 0 : TRC_DEBUG(LOADER, "Failed to get C_type %d data for col %u of row %lu. ODBCmsg: %s\n",
997 : targetType, col+1, row, (ODBCmsg) ? ODBCmsg : "");
998 0 : if (ODBCmsg)
999 0 : GDKfree(ODBCmsg);
1000 :
1001 : /* as all bats need to be the same length, append NULL value */
1002 0 : if (BUNappend(b, ATOMnilptr(b->ttype), false) != GDK_SUCCEED)
1003 0 : TRC_ERROR(LOADER, "BUNappend(b, ATOMnilptr(b->ttype), false) failed after SQLGetData failed\n");
1004 : } else {
1005 66699 : if (strLen == SQL_NULL_DATA) {
1006 1378 : TRC_DEBUG(LOADER, "Data row %lu col %u: NULL\n", row, col+1);
1007 1378 : if (BUNappend(b, ATOMnilptr(b->ttype), false) != GDK_SUCCEED)
1008 0 : TRC_ERROR(LOADER, "BUNappend(b, ATOMnilptr(b->ttype), false) failed for setting SQL_NULL_DATA\n");
1009 : } else {
1010 65321 : switch(sqltype) {
1011 9246 : case SQL_CHAR:
1012 : case SQL_VARCHAR:
1013 : case SQL_LONGVARCHAR:
1014 : case SQL_WCHAR:
1015 : case SQL_WVARCHAR:
1016 : case SQL_WLONGVARCHAR:
1017 : default:
1018 9246 : if (strLen != SQL_NTS && strLen >= 0) {
1019 : /* make sure it is a Nul Terminated String */
1020 9246 : if ((SQLULEN) strLen < largestStringSize) {
1021 9246 : if (str_val[strLen] != '\0')
1022 0 : str_val[strLen] = '\0';
1023 : } else {
1024 0 : if (str_val[largestStringSize] != '\0')
1025 0 : str_val[largestStringSize] = '\0';
1026 : }
1027 : }
1028 9246 : TRC_DEBUG(LOADER, "Data row %lu col %u: %s\n", row, col+1, str_val);
1029 9246 : switch (colmetadata[col].battype) {
1030 9243 : case TYPE_str:
1031 9243 : gdkret = BUNappend(b, (void *) str_val, false);
1032 9243 : break;
1033 : #ifdef HAVE_HGE
1034 3 : case TYPE_hge:
1035 : /* HUGEINT values are read as string */
1036 3 : hge_val = str_to_hge(str_val);
1037 3 : gdkret = BUNappend(b, (void *) &hge_val, false);
1038 3 : break;
1039 : #endif
1040 0 : default:
1041 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1042 0 : break;
1043 : }
1044 : break;
1045 4 : case SQL_BIT:
1046 4 : if (colmetadata[col].battype == TYPE_bit) {
1047 4 : TRC_DEBUG(LOADER, "Data row %lu col %u: %x\n", row, col+1, bit_val);
1048 4 : gdkret = BUNappend(b, (void *) &bit_val, false);
1049 : } else {
1050 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1051 : }
1052 : break;
1053 3 : case SQL_TINYINT:
1054 3 : if (colmetadata[col].battype == TYPE_bte) {
1055 3 : TRC_DEBUG(LOADER, "Data row %lu col %u: %hd\n", row, col+1, (sht) bte_val);
1056 3 : gdkret = BUNappend(b, (void *) &bte_val, false);
1057 : } else {
1058 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1059 : }
1060 : break;
1061 3 : case SQL_SMALLINT:
1062 3 : if (colmetadata[col].battype == TYPE_sht) {
1063 3 : TRC_DEBUG(LOADER, "Data row %lu col %u: %hd\n", row, col+1, sht_val);
1064 3 : gdkret = BUNappend(b, (void *) &sht_val, false);
1065 : } else {
1066 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1067 : }
1068 : break;
1069 49404 : case SQL_INTEGER:
1070 49404 : if (colmetadata[col].battype == TYPE_int) {
1071 49404 : TRC_DEBUG(LOADER, "Data row %lu col %u: %d\n", row, col+1, int_val);
1072 49404 : gdkret = BUNappend(b, (void *) &int_val, false);
1073 : } else {
1074 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1075 : }
1076 : break;
1077 3 : case SQL_BIGINT:
1078 3 : if (colmetadata[col].battype == TYPE_lng) {
1079 3 : TRC_DEBUG(LOADER, "Data row %lu col %u: %" PRId64 "\n", row, col+1, lng_val);
1080 3 : gdkret = BUNappend(b, (void *) &lng_val, false);
1081 : } else {
1082 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1083 : }
1084 : break;
1085 9 : case SQL_DECIMAL:
1086 : case SQL_NUMERIC:
1087 9 : if (colmetadata[col].battype == TYPE_str) {
1088 9 : if (strLen != SQL_NTS && strLen >= 0) {
1089 : /* make sure it is a Nul Terminated String */
1090 9 : if ((SQLULEN) strLen < largestStringSize) {
1091 9 : if (str_val[strLen] != '\0')
1092 0 : str_val[strLen] = '\0';
1093 : } else {
1094 0 : if (str_val[largestStringSize] != '\0')
1095 0 : str_val[largestStringSize] = '\0';
1096 : }
1097 : }
1098 9 : TRC_DEBUG(LOADER, "Data row %lu col %u: %s\n", row, col+1, str_val);
1099 9 : gdkret = BUNappend(b, (void *) str_val, false);
1100 : } else {
1101 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1102 : }
1103 : // TODO add support for battypes: bte, sht, int, lng, hge
1104 : // this requires conversion of string to the target bat type, with the scale (decimalDigits) semantics.
1105 : break;
1106 3 : case SQL_REAL:
1107 3 : if (colmetadata[col].battype == TYPE_flt) {
1108 3 : TRC_DEBUG(LOADER, "Data row %lu col %u: %f\n", row, col+1, flt_val);
1109 3 : gdkret = BUNappend(b, (void *) &flt_val, false);
1110 : } else {
1111 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1112 : }
1113 : break;
1114 6169 : case SQL_DOUBLE:
1115 6169 : if (colmetadata[col].battype == TYPE_dbl) {
1116 6169 : TRC_DEBUG(LOADER, "Data row %lu col %u: %f\n", row, col+1, dbl_val);
1117 6169 : gdkret = BUNappend(b, (void *) &dbl_val, false);
1118 : } else {
1119 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1120 : }
1121 : break;
1122 0 : case SQL_FLOAT:
1123 0 : if (colmetadata[col].battype == TYPE_flt) {
1124 0 : TRC_DEBUG(LOADER, "Data row %lu col %u: %f\n", row, col+1, flt_val);
1125 0 : gdkret = BUNappend(b, (void *) &flt_val, false);
1126 : } else {
1127 0 : TRC_DEBUG(LOADER, "Data row %lu col %u: %f\n", row, col+1, dbl_val);
1128 0 : gdkret = BUNappend(b, (void *) &dbl_val, false);
1129 : }
1130 : break;
1131 3 : case SQL_TYPE_DATE:
1132 3 : if (colmetadata[col].battype == TYPE_date) {
1133 3 : date mdate_val = date_create(date_val.year, date_val.month, date_val.day);
1134 3 : TRC_DEBUG(LOADER, "Data row %lu col %u: date(%04d-%02u-%02u)\n",
1135 : row, col+1, date_val.year, date_val.month, date_val.day);
1136 3 : gdkret = BUNappend(b, (void *) &mdate_val, false);
1137 : } else {
1138 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1139 : }
1140 : break;
1141 3 : case SQL_TYPE_TIME:
1142 3 : if (colmetadata[col].battype == TYPE_daytime) {
1143 3 : daytime daytime_val = daytime_create(time_val.hour, time_val.minute, time_val.second, 0);
1144 3 : TRC_DEBUG(LOADER, "Data row %lu col %u: daytime(%02u:%02u:%02u)\n",
1145 : row, col+1, time_val.hour, time_val.minute, time_val.second);
1146 3 : gdkret = BUNappend(b, (void *) &daytime_val, false);
1147 : } else {
1148 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1149 : }
1150 : break;
1151 426 : case SQL_DATETIME:
1152 : case SQL_TYPE_TIMESTAMP:
1153 426 : if (colmetadata[col].battype == TYPE_timestamp) {
1154 426 : date mdate_val = date_create(ts_val.year, ts_val.month, ts_val.day);
1155 426 : daytime daytime_val = daytime_create(ts_val.hour, ts_val.minute, ts_val.second, ts_val.fraction);
1156 426 : timestamp timestamp_val = timestamp_create(mdate_val, daytime_val);
1157 426 : TRC_DEBUG(LOADER, "Data row %lu col %u: timestamp(%04d-%02u-%02u %02u:%02u:%02u.%06u)\n", row, col+1,
1158 : ts_val.year, ts_val.month, ts_val.day, ts_val.hour, ts_val.minute, ts_val.second, ts_val.fraction);
1159 426 : gdkret = BUNappend(b, (void *) ×tamp_val, false);
1160 : } else {
1161 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1162 : }
1163 : break;
1164 9 : case SQL_INTERVAL_YEAR:
1165 : case SQL_INTERVAL_YEAR_TO_MONTH:
1166 : case SQL_INTERVAL_MONTH:
1167 9 : if (colmetadata[col].battype == TYPE_int) {
1168 9 : switch (itv_val.interval_type) {
1169 3 : case SQL_IS_YEAR:
1170 3 : int_val = (int) itv_val.intval.year_month.year *12;
1171 3 : break;
1172 3 : case SQL_IS_YEAR_TO_MONTH:
1173 3 : int_val = (int) (itv_val.intval.year_month.year *12)
1174 3 : + itv_val.intval.year_month.month;
1175 3 : break;
1176 3 : case SQL_IS_MONTH:
1177 3 : int_val = (int) itv_val.intval.year_month.month;
1178 3 : break;
1179 0 : default:
1180 0 : int_val = 0;
1181 : }
1182 :
1183 9 : if (itv_val.interval_sign == SQL_TRUE)
1184 3 : int_val = -int_val;
1185 9 : TRC_DEBUG(LOADER, "Data row %lu col %u: %d\n", row, col+1, int_val);
1186 9 : gdkret = BUNappend(b, (void *) &int_val, false);
1187 : } else {
1188 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1189 : }
1190 : break;
1191 32 : case SQL_INTERVAL_DAY:
1192 : case SQL_INTERVAL_HOUR:
1193 : case SQL_INTERVAL_MINUTE:
1194 : case SQL_INTERVAL_SECOND:
1195 : case SQL_INTERVAL_DAY_TO_HOUR:
1196 : case SQL_INTERVAL_DAY_TO_MINUTE:
1197 : case SQL_INTERVAL_DAY_TO_SECOND:
1198 : case SQL_INTERVAL_HOUR_TO_MINUTE:
1199 : case SQL_INTERVAL_HOUR_TO_SECOND:
1200 : case SQL_INTERVAL_MINUTE_TO_SECOND:
1201 32 : if (colmetadata[col].battype == TYPE_lng) {
1202 32 : switch (itv_val.interval_type) {
1203 5 : case SQL_IS_DAY:
1204 5 : lng_val = (lng) itv_val.intval.day_second.day * (24*60*60*1000);
1205 5 : break;
1206 3 : case SQL_IS_HOUR:
1207 3 : lng_val = (lng) itv_val.intval.day_second.hour * (60*60*1000);
1208 3 : break;
1209 3 : case SQL_IS_MINUTE:
1210 3 : lng_val = (lng) itv_val.intval.day_second.minute * (60*1000);
1211 3 : break;
1212 3 : case SQL_IS_SECOND:
1213 6 : lng_val = (lng) (itv_val.intval.day_second.second * 1000)
1214 3 : + fraction2msec(itv_val.intval.day_second.fraction, colmetadata[col].decimalDigits);
1215 3 : break;
1216 3 : case SQL_IS_DAY_TO_HOUR:
1217 3 : lng_val = (lng) ((itv_val.intval.day_second.day *24)
1218 3 : + itv_val.intval.day_second.hour) * (60*60*1000);
1219 3 : break;
1220 3 : case SQL_IS_DAY_TO_MINUTE:
1221 3 : lng_val = (lng) ((((itv_val.intval.day_second.day *24)
1222 3 : + itv_val.intval.day_second.hour) *60)
1223 3 : + itv_val.intval.day_second.minute) * (60*1000);
1224 3 : break;
1225 3 : case SQL_IS_DAY_TO_SECOND:
1226 6 : lng_val = (lng) (((((((itv_val.intval.day_second.day *24)
1227 3 : + itv_val.intval.day_second.hour) *60)
1228 3 : + itv_val.intval.day_second.minute) *60)
1229 3 : + itv_val.intval.day_second.second) *1000)
1230 3 : + fraction2msec(itv_val.intval.day_second.fraction, colmetadata[col].decimalDigits);
1231 3 : break;
1232 3 : case SQL_IS_HOUR_TO_MINUTE:
1233 3 : lng_val = (lng) ((itv_val.intval.day_second.hour *60)
1234 3 : + itv_val.intval.day_second.minute) * (60*1000);
1235 3 : break;
1236 3 : case SQL_IS_HOUR_TO_SECOND:
1237 6 : lng_val = (lng) (((((itv_val.intval.day_second.hour *60)
1238 3 : + itv_val.intval.day_second.minute) *60)
1239 3 : + itv_val.intval.day_second.second) *1000)
1240 3 : + fraction2msec(itv_val.intval.day_second.fraction, colmetadata[col].decimalDigits);
1241 3 : break;
1242 3 : case SQL_IS_MINUTE_TO_SECOND:
1243 6 : lng_val = (lng) (((itv_val.intval.day_second.minute *60)
1244 3 : + itv_val.intval.day_second.second) *1000)
1245 3 : + fraction2msec(itv_val.intval.day_second.fraction, colmetadata[col].decimalDigits);
1246 3 : break;
1247 0 : default:
1248 0 : lng_val = 0;
1249 0 : break;
1250 : }
1251 :
1252 32 : if (itv_val.interval_sign == SQL_TRUE)
1253 11 : lng_val = -lng_val;
1254 32 : TRC_DEBUG(LOADER, "Data row %lu col %u: %" PRId64 "\n", row, col+1, lng_val);
1255 32 : gdkret = BUNappend(b, (void *) &lng_val, false);
1256 : } else {
1257 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1258 : }
1259 : break;
1260 2 : case SQL_GUID:
1261 2 : TRC_DEBUG(LOADER, "Data row %lu col %u: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", row, col+1,
1262 : guid_val.Data1, guid_val.Data2, guid_val.Data3, guid_val.Data4[0], guid_val.Data4[1], guid_val.Data4[2],
1263 : guid_val.Data4[3], guid_val.Data4[4], guid_val.Data4[5], guid_val.Data4[6], guid_val.Data4[7]);
1264 2 : if (colmetadata[col].battype == TYPE_uuid) {
1265 2 : u_val.u[0] = (guid_val.Data1 >> 24) & 0xFF;
1266 2 : u_val.u[1] = (guid_val.Data1 >> 16) & 0xFF;
1267 2 : u_val.u[2] = (guid_val.Data1 >> 8) & 0xFF;
1268 2 : u_val.u[3] = guid_val.Data1 & 0xFF;
1269 2 : u_val.u[4] = (guid_val.Data2 >> 8) & 0xFF;
1270 2 : u_val.u[5] = guid_val.Data2 & 0xFF;
1271 2 : u_val.u[6] = (guid_val.Data3 >> 8) & 0xFF;
1272 2 : u_val.u[7] = guid_val.Data3 & 0xFF;
1273 2 : memcpy(&u_val.u[8], &guid_val.Data4[0], 8);
1274 2 : gdkret = BUNappend(b, (void *) &u_val.uuid_val, false);
1275 : } else {
1276 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1277 : }
1278 : break;
1279 2 : case SQL_BINARY:
1280 : case SQL_VARBINARY:
1281 : case SQL_LONGVARBINARY:
1282 2 : TRC_DEBUG(LOADER, "Data row %lu col %u: binary data[%d]\n", row, col+1, (int) strLen);
1283 2 : if (colmetadata[col].battype == TYPE_blob && strLen > 0) {
1284 : // convert bin_data to blob struct.
1285 2 : size_t bin_size = (size_t)strLen;
1286 2 : if (bin_size > (size_t)largestBlobSize)
1287 : /* the data has been truncated */
1288 : bin_size = (size_t)largestBlobSize;
1289 2 : blob * blb = (blob *) GDKmalloc(blobsize(bin_size));
1290 2 : if (blb) {
1291 2 : blb->nitems = bin_size;
1292 2 : memcpy(blb->data, bin_data, bin_size);
1293 2 : gdkret = BUNappend(b, (void *) blb, false);
1294 2 : GDKfree(blb);
1295 : } else {
1296 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1297 : }
1298 : } else {
1299 0 : gdkret = BUNappend(b, ATOMnilptr(b->ttype), false);
1300 : }
1301 : break;
1302 : }
1303 65321 : if (gdkret != GDK_SUCCEED)
1304 0 : TRC_ERROR(LOADER, "BUNappend(b, val, false) failed!\n");
1305 : }
1306 : }
1307 : }
1308 15696 : ret = SQLFetch(stmt); // get data of next row
1309 : }
1310 : /* the last SQLFetch() will return SQL_NO_DATA at end, treat it as success */
1311 51 : if (ret == SQL_NO_DATA)
1312 51 : ret = SQL_SUCCESS; // we retrieved all rows
1313 51 : TRC_INFO(LOADER, "Fetched %lu rows\n", row);
1314 :
1315 51 : finish_fetch:
1316 51 : if (str_val)
1317 51 : GDKfree(str_val);
1318 51 : if (bin_data)
1319 1 : GDKfree(bin_data);
1320 :
1321 : /* pass bats to caller */
1322 : if (colmetadata) {
1323 243 : for (int col = 0; col < (int) nr_cols; col++) {
1324 192 : bat * rescol = getArgReference_bat(stk, pci, col);
1325 192 : BAT * b = colmetadata[col].bat;
1326 192 : if (rescol && b) {
1327 192 : *rescol = b->batCacheid;
1328 192 : BBPkeepref(b);
1329 : }
1330 192 : TRC_DEBUG(LOADER, "col %d pass bat %d\n", col, b->ttype);
1331 : }
1332 : /* free locally allocated memory */
1333 51 : GDKfree(colmetadata);
1334 : }
1335 : } /* end of: if (caller == ODBC_LOADER) */
1336 :
1337 0 : finish:
1338 107 : if (query)
1339 5 : GDKfree(query);
1340 107 : if (odbc_con_str)
1341 4 : GDKfree(odbc_con_str);
1342 :
1343 107 : TRC_DEBUG(LOADER, "caller %d at finish, ret %d (%s) errmsg %s\n", caller, ret, nameOfRetCode(ret), (errmsg) ? errmsg : "");
1344 :
1345 107 : if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
1346 : /* an ODBC function call returned an error or warning, get the error msg from the ODBC driver */
1347 5 : SQLSMALLINT handleType;
1348 5 : SQLHANDLE handle;
1349 5 : str retmsg;
1350 5 : char * ODBCmsg;
1351 :
1352 : /* get err message(s) from the right handle */
1353 5 : if (stmt != SQL_NULL_HSTMT) {
1354 : handleType = SQL_HANDLE_STMT;
1355 : handle = stmt;
1356 : } else
1357 4 : if (dbc != SQL_NULL_HDBC) {
1358 : handleType = SQL_HANDLE_DBC;
1359 : handle = dbc;
1360 : } else {
1361 0 : handleType = SQL_HANDLE_ENV;
1362 0 : handle = env;
1363 : }
1364 5 : ODBCmsg = getErrMsg(handleType, handle);
1365 5 : if (errmsg != NULL) {
1366 5 : retmsg = sa_message(sql->sa, "odbc_loader" " %s %s", errmsg, (ODBCmsg) ? ODBCmsg : "");
1367 : } else {
1368 0 : retmsg = sa_message(sql->sa, "odbc_loader" " %s", (ODBCmsg) ? ODBCmsg : "");
1369 : }
1370 5 : if (ODBCmsg)
1371 5 : GDKfree(ODBCmsg);
1372 5 : odbc_cleanup(env, dbc, stmt);
1373 5 : return retmsg;
1374 : }
1375 :
1376 102 : odbc_cleanup(env, dbc, stmt);
1377 102 : TRC_DEBUG(LOADER, "after odbc_cleanup(%p, %p, %p) errmsg %s\n", env, dbc, stmt, (errmsg) ? errmsg : "");
1378 : return (errmsg != NULL) ? (str)errmsg : MAL_SUCCEED;
1379 : }
1380 :
1381 : /*
1382 : * returns an error string (static or via tmp sa_allocator allocated), NULL on success
1383 : *
1384 : * Extend the subfunc f with result columns, ie.
1385 : f->res = typelist;
1386 : f->coltypes = typelist;
1387 : f->colnames = nameslist; use tname if passed, for the relation name
1388 : * Fill the list res_exps, with one result expressions per resulting column.
1389 : */
1390 : static str
1391 67 : odbc_relation(mvc *sql, sql_subfunc *f, char *url, list *res_exps, char *aname)
1392 : {
1393 67 : (void) aname;
1394 67 : return odbc_query(ODBC_RELATION, sql, f, url, res_exps, NULL, NULL, NULL);
1395 : }
1396 :
1397 : static void *
1398 51 : odbc_load(void *BE, sql_subfunc *f, char *url, sql_exp *topn)
1399 : {
1400 51 : backend *be = (backend*)BE;
1401 51 : if (!f)
1402 : return NULL;
1403 :
1404 51 : (void)topn;
1405 :
1406 51 : InstrPtr q = newStmtArgs(be->mb, "odbc", "loader", list_length(f->coltypes) + 2);
1407 51 : int col = 0;
1408 51 : list *l = sa_list(be->mvc->sa);
1409 243 : for (node *n = f->coltypes->h, *nn = f->colnames->h; n && nn; col++, n = n->next, nn = nn->next) {
1410 192 : const char *name = nn->data;
1411 192 : sql_subtype *tp = n->data;
1412 192 : if (tp) {
1413 192 : int type = newBatType(tp->type->localtype);
1414 192 : if (col)
1415 141 : q = pushReturn(be->mb, q, newTmpVariable(be->mb, type));
1416 : else
1417 51 : getArg(q, 0) = newTmpVariable(be->mb, type);
1418 192 : stmt *s = stmt_blackbox_result(be, q, col, tp);
1419 192 : s = stmt_alias(be, s, col+1, f->tname, name);
1420 192 : list_append(l, s);
1421 : }
1422 : }
1423 51 : q = pushStr(be->mb, q, url);
1424 51 : q = pushPtr(be->mb, q, f);
1425 51 : pushInstruction(be->mb, q);
1426 51 : return stmt_list(be, l);
1427 : }
1428 :
1429 : static str
1430 51 : ODBCloader(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
1431 : {
1432 51 : backend *be = NULL;
1433 51 : str msg;
1434 51 : if ((msg = getBackendContext(cntxt, &be)) != NULL)
1435 : return msg;
1436 51 : str uri = *getArgReference_str(stk, pci, pci->retc);
1437 51 : sql_subfunc *f = *(sql_subfunc**)getArgReference_ptr(stk, pci, pci->retc+1);
1438 :
1439 51 : return odbc_query(ODBC_LOADER, be->mvc, f, uri, NULL, mb, stk, pci);
1440 : //return MAL_SUCCEED;
1441 : }
1442 :
1443 : static str
1444 1 : ODBCprelude(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
1445 : {
1446 1 : (void)cntxt; (void)mb; (void)stk; (void)pci;
1447 1 : pl_register("odbc", &odbc_relation, &odbc_load);
1448 1 : return MAL_SUCCEED;
1449 : }
1450 :
1451 : static str
1452 1 : ODBCepilogue(void *ret)
1453 : {
1454 1 : (void)ret;
1455 1 : pl_unregister("odbc");
1456 1 : return MAL_SUCCEED;
1457 : }
1458 :
1459 : #include "sql_scenario.h"
1460 : #include "mel.h"
1461 :
1462 : static mel_func odbc_init_funcs[] = {
1463 : pattern("odbc", "prelude", ODBCprelude, false, "", noargs),
1464 : command("odbc", "epilogue", ODBCepilogue, false, "", noargs),
1465 : pattern("odbc", "loader", ODBCloader, true, "Import a query result via the odbc uri", args(1,3, batvarargany("",0),arg("uri",str),arg("func",ptr))),
1466 : { .imp=NULL }
1467 : };
1468 :
1469 : #include "mal_import.h"
1470 : #ifdef _MSC_VER
1471 : #undef read
1472 : #pragma section(".CRT$XCU",read)
1473 : #endif
1474 1 : LIB_STARTUP_FUNC(init_odbc_mal)
1475 1 : { mal_module("odbc", NULL, odbc_init_funcs); }
|