/* This file is part of the Q programming system. The Q programming system is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. The Q programming system is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #if defined (HAVE_CONFIG_H) # include "config.h" #endif #ifdef _WIN32 #define STDC_HEADERS 1 #define HAVE_STRDUP 1 #define HAVE_MEMCPY 1 #define HAVE_LIMITS_H 1 #include #endif /* system headers */ #include #include /* check for standard C headers */ #if STDC_HEADERS # include # include #else # ifndef HAVE_STRCHR # define strchr index # define strrchr rindex # endif char *strchr (), *strrchr (); #endif #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include #include #if (ODBCVER < 0x0300) #error "Sorry, this module requires ODBC 3.0 or later!" #endif #include #include #ifdef DMALLOC #include #endif MODULE(odbc) #ifndef HAVE_STRDUP static char *strdup(char *s) { char *t; return ((t=malloc(strlen(s)+1))?strcpy(t, s):NULL); } #endif #ifndef HAVE_MEMCPY #if __GNUC__ > 1 #define memcpy(TO,FROM,COUNT) __builtin_memcpy(TO,FROM,COUNT) #else static void *memcpy (to, from, count) char *to; char *from; int count; { register char *f = from; register char *t = to; register int i = count; while (i-- > 0) *t++ = *f++; return (void*)to; } #endif #endif /* ByteStr data structure, see clib.c */ typedef struct bstr { long size; unsigned char *v; } bstr_t; /* Query parameter structure */ typedef struct { short type; /* SQL parameter type */ short ctype; /* C parameter type */ long len; /* length or indicator */ long buflen; /* real buffer length */ long prec; /* precision */ void *ptr; /* buffer pointer */ union { long iv; /* integer parameter */ double fv; /* floating point parameter */ char *buf; /* string or byte string parameter */ } data; } ODBCParam; /* ODBC handle structure */ typedef struct { SQLHENV henv; /* environment handle */ SQLHDBC hdbc; /* connection handle */ SQLHSTMT hstmt; /* statement handle */ unsigned char exec; /* set while statement is being executed */ short *coltype; /* column types in current result set */ short cols; /* number of columns */ ODBCParam *argv; /* marked parameters */ int argc; /* number of marked parameters */ } ODBCHandle; static int init_args(ODBCHandle *db, int argc) { int i; if (!(db->argv = malloc(argc*sizeof(ODBCParam)))) return 0; db->argc = argc; for (i = 0; i < argc; i++) { db->argv[i].type = SQL_UNKNOWN_TYPE; db->argv[i].len = SQL_NULL_DATA; } return 1; } static void free_args(ODBCHandle *db) { if (db->argv) { int i; SQLFreeStmt(db->hstmt, SQL_RESET_PARAMS); for (i = 0; i < db->argc; i++) if ((db->argv[i].type == SQL_BIGINT || db->argv[i].type == SQL_CHAR || db->argv[i].type == SQL_BINARY) && db->argv[i].data.buf) free(db->argv[i].data.buf); free(db->argv); db->argv = NULL; db->argc = 0; } } static int set_arg(ODBCHandle *db, int i, expr x) { long iv; double fv; char *s; bstr_t *m; mpz_t z; if (isint(x, &iv)) { db->argv[i].type = SQL_INTEGER; db->argv[i].ctype = SQL_C_SLONG; db->argv[i].len = sizeof(long); db->argv[i].buflen = sizeof(long); db->argv[i].prec = 10; db->argv[i].data.iv = iv; db->argv[i].ptr = &db->argv[i].data.iv; return 1; } else if (ismpz(x, z)) { /* convert big integer values to BIGINTs via a string representation, so we don't have to fiddle with long long's here */ db->argv[i].type = SQL_BIGINT; db->argv[i].ctype = SQL_C_CHAR; db->argv[i].len = SQL_NTS; db->argv[i].data.buf = mpz_get_str(NULL, 10, z); if (!db->argv[i].data.buf) return 0; db->argv[i].buflen = strlen(db->argv[i].data.buf)+1; db->argv[i].prec = db->argv[i].buflen-1; db->argv[i].ptr = db->argv[i].data.buf; return 1; } else if (isfloat(x, &fv)) { db->argv[i].type = SQL_DOUBLE; db->argv[i].ctype = SQL_C_DOUBLE; db->argv[i].len = sizeof(double); db->argv[i].buflen = sizeof(double); db->argv[i].prec = 15; db->argv[i].data.fv = fv; db->argv[i].ptr = &db->argv[i].data.fv; return 1; } else if (isstr(x, &s)) { db->argv[i].type = SQL_CHAR; db->argv[i].ctype = SQL_C_CHAR; db->argv[i].len = SQL_NTS; db->argv[i].buflen = strlen(s)+1; db->argv[i].prec = db->argv[i].buflen-1; db->argv[i].data.buf = strdup(s); db->argv[i].ptr = db->argv[i].data.buf; return db->argv[i].data.buf != NULL; } else if (isobj(x, type(ByteStr), (void**)&m)) { db->argv[i].type = SQL_BINARY; db->argv[i].ctype = SQL_C_BINARY; db->argv[i].len = m->size; db->argv[i].buflen = m->size; db->argv[i].prec = m->size; if (m->size > 0) { if (!(db->argv[i].data.buf = malloc(m->size))) return 0; memcpy(db->argv[i].data.buf, m->v, m->size); } else db->argv[i].data.buf = NULL; db->argv[i].ptr = db->argv[i].data.buf; return 1; } else if (isvoid(x)) { db->argv[i].type = SQL_CHAR; db->argv[i].ctype = SQL_C_DEFAULT; db->argv[i].len = SQL_NULL_DATA; db->argv[i].buflen = 0; db->argv[i].prec = 0; db->argv[i].data.buf = NULL; db->argv[i].ptr = NULL; return 1; } else return 0; } static void sql_close(ODBCHandle *db) { if (db->exec) { if (db->coltype) free(db->coltype); free_args(db); SQLFreeStmt(db->hstmt, SQL_CLOSE); db->coltype = NULL; db->cols = 0; db->exec = 0; } } DESTRUCTOR(odbc,ODBCHandle,ptr) { ODBCHandle *db = (ODBCHandle*)ptr; if (db->henv) { sql_close(db); SQLCloseCursor(db->hstmt); SQLFreeHandle(SQL_HANDLE_STMT, db->hstmt); SQLDisconnect(db->hdbc); SQLFreeHandle(SQL_HANDLE_DBC, db->hdbc); SQLFreeHandle(SQL_HANDLE_ENV, db->henv); } free(db); } static expr mkerr(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt) { char stat[10], msg[300]; SQLINTEGER err; short len; /* check for SQL statement errors */ if (hstmt && SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, 1, stat, &err, msg, sizeof(msg), &len) == SQL_SUCCESS) goto exit; /* check for connection errors */ if (hdbc && SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, stat, &err, msg, sizeof(msg), &len) == SQL_SUCCESS) goto exit; /* check for environment errors */ if (henv && SQLGetDiagRec(SQL_HANDLE_ENV, henv, 1, stat, &err, msg, sizeof(msg), &len) == SQL_SUCCESS) goto exit; return __FAIL; exit: return mkapp(mkapp(mksym(sym(odbc_error)), mkstr(strdup(msg))), mkstr(strdup(stat))); } FUNCTION(odbc,odbc_sources,argc,argv) { if (argc == 0) { SQLHENV henv; long ret; expr *xv; int n; char l_dsn[100],l_desc[100]; short l_len1, l_len2, l_next; /* create an environment handle */ if ((ret = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) return __FAIL; if ((ret = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { expr msg = mkerr(henv, 0, 0); SQLFreeHandle(SQL_HANDLE_ENV, henv); return msg; } /* count the number of data sources */ for (n = 0, l_next = SQL_FETCH_FIRST; SQLDataSources(henv, l_next, l_dsn, sizeof(l_dsn), &l_len1, l_desc, sizeof(l_desc), &l_len2) == SQL_SUCCESS; l_next = SQL_FETCH_NEXT) n++; if (!(xv = malloc(n*sizeof(expr)))) { SQLFreeHandle(SQL_HANDLE_ENV, henv); return __ERROR; } /* retrieve the data source names and descriptions */ for (n = 0, l_next = SQL_FETCH_FIRST; SQLDataSources(henv, l_next, l_dsn, sizeof(l_dsn), &l_len1, l_desc, sizeof(l_desc), &l_len2) == SQL_SUCCESS; l_next = SQL_FETCH_NEXT) xv[n++] = mktuplel(2, mkstr(strdup(l_dsn)), mkstr(strdup(l_desc))); /* free the environment handle */ SQLFreeHandle(SQL_HANDLE_ENV, henv); return mklistv(n, xv); } else return __FAIL; } FUNCTION(odbc,odbc_drivers,argc,argv) { if (argc == 0) { SQLHENV henv; long ret; expr *xv; int n; char l_drv[100],l_attr[10000]; short l_len1, l_len2, l_next; /* create an environment handle */ if ((ret = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) return __FAIL; if ((ret = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { expr msg = mkerr(henv, 0, 0); SQLFreeHandle(SQL_HANDLE_ENV, henv); return msg; } /* count the number of driver descriptions */ for (n = 0, l_next = SQL_FETCH_FIRST; SQLDrivers(henv, l_next, l_drv, sizeof(l_drv), &l_len1, l_attr, sizeof(l_attr), &l_len2) == SQL_SUCCESS; l_next = SQL_FETCH_NEXT) n++; if (!(xv = malloc(n*sizeof(expr)))) { SQLFreeHandle(SQL_HANDLE_ENV, henv); return __ERROR; } /* retrieve the driver and descriptions */ for (n = 0, l_next = SQL_FETCH_FIRST; SQLDrivers(henv, l_next, l_drv, sizeof(l_drv), &l_len1, l_attr, sizeof(l_attr), &l_len2) == SQL_SUCCESS; l_next = SQL_FETCH_NEXT) { int k; char *l_attrp; expr *yv; /* count the number of attributes */ for (k = 0, l_attrp = l_attr; *l_attrp; l_attrp = l_attrp+strlen(l_attrp)+1) k++; if (!(yv = malloc(k*sizeof(expr)))) { int i; for (i = 0; i < n; i++) dispose(xv[i]); free(xv); SQLFreeHandle(SQL_HANDLE_ENV, henv); return __ERROR; } /* get the attribute strings */ for (k = 0, l_attrp = l_attr; *l_attrp; l_attrp = l_attrp+strlen(l_attrp)+1) yv[k++] = mkstr(strdup(l_attrp)); xv[n++] = mktuplel(2, mkstr(strdup(l_drv)), mklistv(k, yv)); } /* free the environment handle */ SQLFreeHandle(SQL_HANDLE_ENV, henv); return mklistv(n, xv); } else return __FAIL; } FUNCTION(odbc,odbc_connect,argc,argv) { char *conn; if (argc == 1 && isstr(argv[0], &conn)) { ODBCHandle *db = (ODBCHandle*)malloc(sizeof(ODBCHandle)); long ret; short buflen; char buf[1024]; if (!db) return __ERROR; /* create the environment handle */ if ((ret = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &db->henv)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) return __FAIL; if ((ret = SQLSetEnvAttr(db->henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { expr msg = mkerr(db->henv, 0, 0); SQLFreeHandle(SQL_HANDLE_ENV, db->henv); return msg; } /* create the connection handle */ if ((ret = SQLAllocHandle(SQL_HANDLE_DBC, db->henv, &db->hdbc)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { expr msg = mkerr(db->henv, 0, 0); SQLFreeHandle(SQL_HANDLE_ENV, db->henv); return msg; } /* connect */ if ((ret = SQLDriverConnect(db->hdbc, 0, conn, SQL_NTS, buf, sizeof(buf), &buflen, SQL_DRIVER_NOPROMPT)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { expr msg = mkerr(db->henv, db->hdbc, 0); SQLFreeHandle(SQL_HANDLE_DBC, db->hdbc); SQLFreeHandle(SQL_HANDLE_ENV, db->henv); return msg; } /* create the statement handle */ if ((ret = SQLAllocHandle(SQL_HANDLE_STMT, db->hdbc, &db->hstmt)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { expr msg = mkerr(db->henv, db->hdbc, 0); SQLDisconnect(db->hdbc); SQLFreeHandle(SQL_HANDLE_DBC, db->hdbc); SQLFreeHandle(SQL_HANDLE_ENV, db->henv); return msg; } /* initialize statement properties */ db->argv = NULL; db->argc = 0; db->coltype = NULL; db->cols = 0; db->exec = 0; /* return the result */ return mkobj(type(ODBCHandle), db); } else return __FAIL; } FUNCTION(odbc,odbc_disconnect,argc,argv) { ODBCHandle *db; if (argc == 1 && isobj(argv[0], type(ODBCHandle), (void**)&db) && db->henv) { sql_close(db); SQLCloseCursor(db->hstmt); SQLFreeHandle(SQL_HANDLE_STMT, db->hstmt); db->hstmt = 0; SQLDisconnect(db->hdbc); SQLFreeHandle(SQL_HANDLE_DBC, db->hdbc); db->hdbc = 0; SQLFreeHandle(SQL_HANDLE_ENV, db->henv); db->henv = 0; return mkvoid; } else return __FAIL; } FUNCTION(odbc,odbc_info,argc,argv) { ODBCHandle *db; if (argc == 1 && isobj(argv[0], type(ODBCHandle), (void**)&db) && db->henv) { long ret; int n = 0; expr *xv = malloc(8*sizeof(expr)); char info[1024]; short len; if (!xv) return __ERROR; if ((ret = SQLGetInfo(db->hdbc, SQL_DATA_SOURCE_NAME, info, sizeof(info), &len)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) xv[n++] = mkstr(strdup(info)); else xv[n++] = mkstr(strdup("")); if ((ret = SQLGetInfo(db->hdbc, SQL_DATABASE_NAME, info, sizeof(info), &len)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) xv[n++] = mkstr(strdup(info)); else xv[n++] = mkstr(strdup("")); if ((ret = SQLGetInfo(db->hdbc, SQL_DBMS_NAME, info, sizeof(info), &len)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) xv[n++] = mkstr(strdup(info)); else xv[n++] = mkstr(strdup("")); if ((ret = SQLGetInfo(db->hdbc, SQL_DBMS_VER, info, sizeof(info), &len)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) xv[n++] = mkstr(strdup(info)); else xv[n++] = mkstr(strdup("")); if ((ret = SQLGetInfo(db->hdbc, SQL_DRIVER_NAME, info, sizeof(info), &len)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) xv[n++] = mkstr(strdup(info)); else xv[n++] = mkstr(strdup("")); if ((ret = SQLGetInfo(db->hdbc, SQL_DRIVER_VER, info, sizeof(info), &len)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) xv[n++] = mkstr(strdup(info)); else xv[n++] = mkstr(strdup("")); if ((ret = SQLGetInfo(db->hdbc, SQL_DRIVER_ODBC_VER, info, sizeof(info), &len)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) xv[n++] = mkstr(strdup(info)); else xv[n++] = mkstr(strdup("")); if ((ret = SQLGetInfo(db->hdbc, SQL_ODBC_VER, info, sizeof(info), &len)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) xv[n++] = mkstr(strdup(info)); else xv[n++] = mkstr(strdup("")); return mktuplev(n, xv); } else return __FAIL; } #define BUFSZ 65536 #define BUFSZ2 5000 FUNCTION(odbc,sql_exec,argc,argv) { ODBCHandle *db; char *query; if (argc == 3 && isobj(argv[0], type(ODBCHandle), (void**)&db) && db->henv && isstr(argv[1], &query)) { long ret; expr res, *xs, *xv; int n; short i, cols, *coltype = NULL; char buf[BUFSZ2]; /* finalize previous query */ sql_close(db); /* prepare statement */ if ((ret = SQLPrepare(db->hstmt, query, SQL_NTS)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) return mkerr(db->henv, db->hdbc, db->hstmt); /* bind parameters */ if (isvoid(argv[2])) ; else if (istuple(argv[2], &n, &xv)) { if (!init_args(db, n)) goto fatal; for (i = 0; i < n; i++) if (!set_arg(db, i, xv[i])) { int alloc_error = (db->argv[i].type == SQL_BIGINT || db->argv[i].type == SQL_CHAR || db->argv[i].type == SQL_BINARY) && !db->argv[i].data.buf; free_args(db); if (alloc_error) goto fatal; else goto fail; } } else { if (!init_args(db, 1)) goto fatal; if (!set_arg(db, 0, argv[2])) { int alloc_error = (db->argv[0].type == SQL_BIGINT || db->argv[0].type == SQL_CHAR || db->argv[0].type == SQL_BINARY) && !db->argv[0].data.buf; free_args(db); if (alloc_error) goto fatal; else goto fail; } } for (i = 0; i < db->argc; i++) if ((ret = SQLBindParameter(db->hstmt, i+1, SQL_PARAM_INPUT, db->argv[i].ctype, db->argv[i].type, db->argv[i].prec, 0, db->argv[i].ptr, db->argv[i].buflen, &db->argv[i].len)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) goto err; /* execute statement */ if ((ret = SQLExecute(db->hstmt)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) return mkerr(db->henv, db->hdbc, db->hstmt); /* determine the number of columns */ if ((ret = SQLNumResultCols(db->hstmt, &cols)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) goto err; if (cols == 0) { SQLINTEGER rows; if ((ret = SQLRowCount(db->hstmt, &rows)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) res = mkint((long)rows); else res = mkint(0); db->exec = 1; goto exit; } /* get the column names and types */ if (!(coltype = malloc(cols*sizeof(short)))) goto fatal; if (!(xs = malloc(cols*sizeof(expr)))) goto fatal; for (i = 0; i < cols; i++) { buf[0] = 0; if ((ret = SQLDescribeCol(db->hstmt, i+1, buf, sizeof(buf), NULL, &coltype[i], NULL, NULL, NULL)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { int j; for (j = 0; j < i; j++) dispose(xs[j]); free(xs); goto err; } xs[i] = mkstr(strdup(buf)); } res = mktuplev(cols, xs); if (res) { db->coltype = coltype; db->cols = cols; coltype = NULL; db->exec = 1; } else { free_args(db); SQLFreeStmt(db->hstmt, SQL_CLOSE); } goto exit; fail: free_args(db); SQLFreeStmt(db->hstmt, SQL_CLOSE); res = __FAIL; goto exit; err: free_args(db); SQLFreeStmt(db->hstmt, SQL_CLOSE); res = mkerr(db->henv, db->hdbc, db->hstmt); goto exit; fatal: free_args(db); SQLFreeStmt(db->hstmt, SQL_CLOSE); res = __ERROR; exit: if (coltype) free(coltype); return res; } else return __FAIL; } FUNCTION(odbc,sql_fetch,argc,argv) { ODBCHandle *db; if (argc == 1 && isobj(argv[0], type(ODBCHandle), (void**)&db) && db->henv && db->coltype) { long ret; expr res, *xs; short i, j, cols = db->cols, *coltype = db->coltype; long iv, sz = BUFSZ; double fv; char *buf = malloc(sz); SDWORD len; if (!buf) goto fatal; /* fetch the next record */ if ((ret = SQLFetch(db->hstmt)) == SQL_NO_DATA_FOUND) { res = __FAIL; goto exit; } else if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) goto err; if (!(xs = malloc(cols*sizeof(expr)))) goto fatal; /* get the columns */ for (i = 0; i < cols; i++) { switch (coltype[i]) { case SQL_BIT: case SQL_TINYINT: case SQL_SMALLINT: case SQL_INTEGER: if ((ret = SQLGetData(db->hstmt, i+1, SQL_INTEGER, &iv, sizeof(iv), &len) != SQL_SUCCESS) && ret != SQL_SUCCESS_WITH_INFO) goto err2; if (len == SQL_NULL_DATA) xs[i] = mkvoid; else xs[i] = mkint(iv); break; case SQL_BIGINT: /* hack to get bigint values converted to mpz_t, without having to fiddle around with long long values XXXFIXME: we should really avoid the string conversion here */ if ((ret = SQLGetData(db->hstmt, i+1, SQL_CHAR, buf, sz, &len) != SQL_SUCCESS) && ret != SQL_SUCCESS_WITH_INFO) goto err2; if (len == SQL_NULL_DATA) xs[i] = mkvoid; else { mpz_t z; mpz_init(z); mpz_set_str(z, buf, 0); xs[i] = mkmpz(z); } break; case SQL_DOUBLE: case SQL_DECIMAL: case SQL_NUMERIC: case SQL_FLOAT: case SQL_REAL: if ((ret = SQLGetData(db->hstmt, i+1, SQL_DOUBLE, &fv, sizeof(fv), &len) != SQL_SUCCESS) && ret != SQL_SUCCESS_WITH_INFO) goto err2; if (len == SQL_NULL_DATA) xs[i] = mkvoid; else xs[i] = mkfloat(fv); break; case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: { char *bufp = buf; long total = 0, actsz = sz; *buf = 0; while (1) { if ((ret = SQLGetData(db->hstmt, i+1, SQL_BINARY, bufp, actsz, &len)) == SQL_SUCCESS || ret == SQL_NO_DATA) { if (len == SQL_NULL_DATA) break; if (INT_MAX - len <= total) goto fatal2; else total += len; break; } else if (ret == SQL_SUCCESS_WITH_INFO) { /* we probably need to make room for additional data */ char *buf1; if (len == SQL_NULL_DATA) break; if (INT_MAX - BUFSZ <= total) goto fatal2; else total += actsz; if (!(buf1 = realloc(buf, sz+BUFSZ))) goto fatal2; buf = buf1; bufp = buf+total; sz += BUFSZ; actsz = BUFSZ; } else { /* some other error, bail out */ goto err2; } } if (len == SQL_NULL_DATA) xs[i] = mkvoid; else if (total == 0) { bstr_t *m; if (!(m = malloc(sizeof(bstr_t)))) goto fatal2; m->size = 0; m->v = NULL; xs[i] = mkobj(type(ByteStr), m); } else { char *buf1 = realloc(buf, total); bstr_t *m; if (buf1) buf = buf1; if (!(m = malloc(sizeof(bstr_t)))) goto fatal2; m->size = total; m->v = buf; xs[i] = mkobj(type(ByteStr), m); /* make a new buffer */ if (!(buf = malloc(BUFSZ))) goto fatal2; else sz = BUFSZ; } break; } default: { char *bufp = buf; long total = 0, actsz = sz; *buf = 0; while (1) { if ((ret = SQLGetData(db->hstmt, i+1, SQL_CHAR, bufp, actsz, &len)) == SQL_SUCCESS || ret == SQL_NO_DATA) { if (len == SQL_NULL_DATA) break; if (INT_MAX - len <= total) goto fatal2; else total += len; break; } else if (ret == SQL_SUCCESS_WITH_INFO) { /* we probably need to make room for additional data */ char *buf1; if (len == SQL_NULL_DATA) break; if (INT_MAX - BUFSZ <= total) goto fatal2; else total += actsz-1; if (!(buf1 = realloc(buf, sz+BUFSZ))) goto fatal2; buf = buf1; bufp = buf+total; sz += BUFSZ; actsz = BUFSZ+1; } else { /* some other error, bail out */ goto err2; } } if (len == SQL_NULL_DATA) xs[i] = mkvoid; else if (sz == BUFSZ) xs[i] = mkstr(strdup(buf)); else { /* avoid copying the (potentially large) buffer, instead make a new one */ char *buf1 = realloc(buf, total); if (buf1) buf = buf1; xs[i] = mkstr(buf); if (!(buf = malloc(BUFSZ))) goto fatal2; else sz = BUFSZ; } } } } res = mktuplev(cols, xs); goto exit; err2: for (j = 0; j < i; j++) dispose(xs[j]); free(xs); err: res = mkerr(db->henv, db->hdbc, db->hstmt); goto exit; fatal2: for (j = 0; j < i; j++) dispose(xs[j]); free(xs); fatal: res = __ERROR; exit: if (buf) free(buf); return res; } else return __FAIL; } FUNCTION(odbc,sql_more,argc,argv) { ODBCHandle *db; if (argc == 1 && isobj(argv[0], type(ODBCHandle), (void**)&db) && db->henv && db->exec) { long ret; expr res, *xs; short i, cols, *coltype = NULL; char buf[BUFSZ2]; SDWORD len; /* get the next result set */ if ((ret = SQLMoreResults(db->hstmt)) == SQL_NO_DATA_FOUND) { res = __FAIL; goto exit; } else if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) goto err; /* determine the number of columns */ if ((ret = SQLNumResultCols(db->hstmt, &cols)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) goto err; if (cols == 0) { SQLINTEGER rows; if ((ret = SQLRowCount(db->hstmt, &rows)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) res = mkint((long)rows); else res = mkint(0); if (db->coltype) free(db->coltype); db->coltype = NULL; db->cols = 0; goto exit; } /* get the column names and types */ if (!(coltype = malloc(cols*sizeof(short)))) goto fatal; if (!(xs = malloc(cols*sizeof(expr)))) goto fatal; for (i = 0; i < cols; i++) { buf[0] = 0; if ((ret = SQLDescribeCol(db->hstmt, i+1, buf, sizeof(buf), NULL, &coltype[i], NULL, NULL, NULL)) != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { int j; for (j = 0; j < i; j++) dispose(xs[j]); free(xs); goto err; } xs[i] = mkstr(strdup(buf)); } res = mktuplev(cols, xs); if (res) { free(db->coltype); if (db->coltype) db->coltype = coltype; db->cols = cols; coltype = NULL; } goto exit; err: res = mkerr(db->henv, db->hdbc, db->hstmt); goto exit; fatal: res = __ERROR; exit: if (coltype) free(coltype); return res; } else return __FAIL; } FUNCTION(odbc,sql_close,argc,argv) { ODBCHandle *db; if (argc == 1 && isobj(argv[0], type(ODBCHandle), (void**)&db) && db->henv && db->exec) { sql_close(db); return mkvoid; } else return __FAIL; }