Changeset: 4f0aacee0d16 for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB/rev/4f0aacee0d16
Modified Files:
        sql/backends/monet5/vaults/odbc/CMakeLists.txt
        sql/backends/monet5/vaults/odbc/odbc_loader.c
        sql/test/proto_loader/odbc/Tests/incomplete_uri.test
        sql/test/proto_loader/odbc/Tests/monetodbc-Windows.test
        sql/test/proto_loader/odbc/Tests/monetodbc.test
Branch: Mar2025
Log Message:

Implement reading decimals. Also now using WCHAR functions for SQLDriverConnect 
and SQLExecDirect.


diffs (truncated from 532 to 300 lines):

diff --git a/sql/backends/monet5/vaults/odbc/CMakeLists.txt 
b/sql/backends/monet5/vaults/odbc/CMakeLists.txt
--- a/sql/backends/monet5/vaults/odbc/CMakeLists.txt
+++ b/sql/backends/monet5/vaults/odbc/CMakeLists.txt
@@ -37,6 +37,7 @@ if(ODBC_FOUND)
     sql
     monetdb5
     bat
+    mutils
     ODBC::ODBC)
 
   set_target_properties(odbc_loader
diff --git a/sql/backends/monet5/vaults/odbc/odbc_loader.c 
b/sql/backends/monet5/vaults/odbc/odbc_loader.c
--- a/sql/backends/monet5/vaults/odbc/odbc_loader.c
+++ b/sql/backends/monet5/vaults/odbc/odbc_loader.c
@@ -18,13 +18,15 @@
 #include "mal_exception.h"
 #include "mal_builder.h"
 #include "mal_client.h"
+#include "mutils.h"    /* utf8towchar(), wchartoutf8() */
+#include "sql_decimal.h"       /* decimal_from_str() */
 
-//#ifdef _MSC_VER
-//#include <WTypes.h>
-//#endif
-//#include <stdint.h>
-//#include <ctype.h>
-//#include <wchar.h>
+#ifdef _MSC_VER
+#include <WTypes.h>
+#endif
+#include <stdint.h>
+#include <ctype.h>
+#include <wchar.h>
 
 /**** Define the ODBC Version our ODBC application complies with ****/
 #define ODBCVER 0x0352         /* Important: this must be defined before 
include of sql.h and sqlext.h */
@@ -39,10 +41,16 @@
 #define MAX_COL_NAME_LEN  1024
 #define MAX_TBL_NAME_LEN  1024
 
+#ifdef HAVE_HGE
+#define MAX_PREC  38
+#else
+#define MAX_PREC  18
+#endif
+
 typedef struct {
        SQLSMALLINT dataType;           /* ODBC datatype */
-       SQLULEN columnSize;             /* ODBC colsize */
-       SQLSMALLINT decimalDigits;      /* ODBC dec. digits */
+       SQLULEN columnSize;             /* ODBC colsize, contains precision for 
decimals */
+       SQLSMALLINT decimalDigits;      /* ODBC dec. digits, contains scale for 
decimals */
        int battype;                    /* MonetDB atom type, used to create 
the BAT */
        BAT * bat;                      /* MonetDB BAT */
 } rescol_t;
@@ -52,7 +60,7 @@ static sql_subtype *
 map_rescol_type(SQLSMALLINT dataType, SQLULEN columnSize, SQLSMALLINT 
decimalDigits, mvc * sql)
 {
        char * typenm;
-       int interval_type = 0;
+       unsigned int interval_type = 0;
 
        switch (dataType) {
        case SQL_CHAR:
@@ -63,18 +71,35 @@ map_rescol_type(SQLSMALLINT dataType, SQ
        case SQL_WLONGVARCHAR:
        default:        /* all other ODBC types are also mapped to varchar for 
now */
                /* all ODBC char datatypes are mapped to varchar. char and clob 
are internally not used anymore */
-               return sql_bind_subtype(sql->sa, "varchar", (int) columnSize, 
0);
+               if (columnSize > (SQLULEN) INT_MAX)
+                       columnSize = INT_MAX;
+               return sql_bind_subtype(sql->sa, "varchar", (unsigned int) 
columnSize, 0);
 
        case SQL_BINARY:
        case SQL_VARBINARY:
        case SQL_LONGVARBINARY:
-               return sql_bind_subtype(sql->sa, "blob", (int) columnSize, 0);
+               if (columnSize > (SQLULEN) INT_MAX)
+                       columnSize = INT_MAX;
+               return sql_bind_subtype(sql->sa, "blob", (unsigned int) 
columnSize, 0);
 
        case SQL_DECIMAL:
        case SQL_NUMERIC:
+       {
                /* columnSize contains the defined number of digits, so 
precision. */
                /* decimalDigits contains the scale (which can be negative). */
-               return sql_bind_subtype(sql->sa, "decimal", (int) columnSize, 
(int) decimalDigits);
+               if (columnSize > MAX_PREC || abs(decimalDigits) > MAX_PREC) {
+                       /* too large precision/scale, not supported by MonetDB. 
Map this column to a string */
+                       if (columnSize > (SQLULEN) INT_MAX)
+                               columnSize = INT_MAX;
+                       return sql_bind_subtype(sql->sa, "varchar", (unsigned 
int) columnSize, 0);
+               }
+
+               unsigned int prec = MIN(1, columnSize); /* precision must be >= 
1 */
+               unsigned int scale = MIN(0, decimalDigits); /* negative scales 
are not supported by MonetDB */
+               if (prec < scale)
+                       prec = scale;   /* make precision large enough to 
contain all decimal digits */
+               return sql_bind_subtype(sql->sa, "decimal", prec, scale);
+       }
 
        case SQL_BIT:
                typenm = "boolean";
@@ -119,10 +144,6 @@ map_rescol_type(SQLSMALLINT dataType, SQ
                typenm = "timestamp";
                break;
 
-       case SQL_INTERVAL_MONTH:
-               typenm = "month_interval";
-               interval_type = 3;
-               break;
        case SQL_INTERVAL_YEAR:
                typenm = "month_interval";
                interval_type = 1;
@@ -131,6 +152,10 @@ map_rescol_type(SQLSMALLINT dataType, SQ
                typenm = "month_interval";
                interval_type = 2;
                break;
+       case SQL_INTERVAL_MONTH:
+               typenm = "month_interval";
+               interval_type = 3;
+               break;
        case SQL_INTERVAL_DAY:
                typenm = "day_interval";
                interval_type = 4;
@@ -273,49 +298,6 @@ bat_create(int adt, BUN nr)
        return b;
 }
 
-static lng
-convert_numericstr2lng(str val, int columnSize, SQLSMALLINT decimalDigits) {
-       lng ret = 0;
-       int i = 0;
-       int digits = 0;
-       int decdigits = 0;
-       char c;
-       char sign = '+';
-       int decsep = -1;
-
-       if (!val)
-               return 0;
-
-       c = val[i];
-       if (c == '-' || c == '+') {
-               sign = c;
-               i++;
-       }
-       while (val[i]) {
-               if (digits >= columnSize || decdigits >= decimalDigits)
-                       break;  // we have read enough
-               c = val[i];
-               if (c >= '0' && c <= '9') {
-                       ret *= 10;
-                       ret += (int) c - '0';
-                       digits++;
-                       if (decsep >= 0)
-                               decdigits++;
-               } else if (c == '.') {
-                       decsep = i;
-               }
-               i++;
-       }
-       while (decdigits < decimalDigits) {
-               // align to the scale
-               ret *= 10;
-               decdigits++;
-       }
-       if (sign == '-')
-               ret = -ret;
-       return ret;
-}
-
 /*
  * odbc_query() contains the logic for both odbc_relation() and ODBCloader()
  * the caller argument is ODBC_RELATION when called from odbc_relation and 
ODBC_LOADER when called from ODBCloader
@@ -350,11 +332,15 @@ odbc_query(int caller, mvc *sql, sql_sub
                return "Incomplete ODBC connection string. Missing 'QUERY=' 
part (to specify the SQL SELECT query to execute).";
 
        char * query = GDKstrdup(&qry_str[6]);  // we expect that QUERY= is at 
the end of the connection string
+       if (query == NULL || (query && (strcmp("", query) == 0)))
+               return "Incomplete ODBC connection string. Missing SQL SELECT 
query after 'QUERY='.";
 
        // create a new ODBC connection string without the QUERY= part
        char * odbc_con_str = GDKstrndup(con_str, qry_str - con_str);
-
-       // TODO convert con_str and qry_str from UTF-8 to UCS16, so we can use 
ODBC W functions
+       if (odbc_con_str == NULL) {
+               GDKfree(query);
+               return "Missing ODBC connection string.";
+       }
 
        // trace_enabled = true;
        if (trace_enabled)
@@ -390,15 +376,23 @@ odbc_query(int caller, mvc *sql, sql_sub
                goto finish;
        }
 
-       // TODO convert con_str from UTF-8 to UCS16, so we can use ODBC W 
functions
        SQLSMALLINT len = 0;
-       ret = SQLDriverConnect(dbc, NULL, (SQLCHAR *) odbc_con_str, SQL_NTS, 
NULL, 0, &len, SQL_DRIVER_NOPROMPT);
+       wchar_t * odbc_con_Wstr = utf8towchar(odbc_con_str);
+       if (odbc_con_Wstr != NULL) {
+               ret = SQLDriverConnectW(dbc, NULL, (SQLWCHAR *) odbc_con_Wstr, 
SQL_NTS, NULL, 0, &len, SQL_DRIVER_NOPROMPT);
+               /* we no longer need odbc_con_Wstr */
+               free(odbc_con_Wstr);
+       } else
+               ret = SQLDriverConnect(dbc, NULL, (SQLCHAR *) odbc_con_str, 
SQL_NTS, NULL, 0, &len, SQL_DRIVER_NOPROMPT);
        if (trace_enabled)
                printf("After SQLDriverConnect(%s) returned %d\n", 
odbc_con_str, ret);
        if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
-               errmsg = "Could not connect. SQLDriverConnect failed.";
+               errmsg = "SQLDriverConnect failed.";
                goto finish;
        }
+       /* we no longer need odbc_con_str */
+       GDKfree(odbc_con_str);
+       odbc_con_str = NULL;
 
        ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
        if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
@@ -406,14 +400,22 @@ odbc_query(int caller, mvc *sql, sql_sub
                goto finish;
        }
 
-       // TODO convert qry_str from UTF-8 to UCS16, so we can use ODBC W 
functions
-       ret = SQLExecDirect(stmt, (SQLCHAR *) query, SQL_NTS);
+       wchar_t * query_Wstr = utf8towchar(query);
+       if (query_Wstr != NULL) {
+               ret = SQLExecDirectW(stmt, (SQLWCHAR *) query_Wstr, SQL_NTS);
+               /* we no longer need query_Wstr */
+               free(query_Wstr);
+       } else
+               ret = SQLExecDirect(stmt, (SQLCHAR *) query, SQL_NTS);
        if (trace_enabled)
                printf("After SQLExecDirect(%s) returned %d\n", query, ret);
        if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
                errmsg = "SQLExecDirect query failed.";
                goto finish;
        }
+       /* we no longer need query string */
+       GDKfree(query);
+       query = NULL;
 
        SQLSMALLINT nr_cols = 0;
        ret = SQLNumResultCols(stmt, &nr_cols);
@@ -518,8 +520,21 @@ odbc_query(int caller, mvc *sql, sql_sub
                                GDKfree(colmetadata);
                                goto finish;
                        }
+                       /* use same logic as used in map_rescol_type() for 
SQL_FLOAT and SQL_DECIMAL */
                        if (dataType == SQL_FLOAT) {
                                dataType = (columnSize == 7) ? SQL_REAL : 
SQL_DOUBLE;
+                       } else
+                       if (dataType == SQL_DECIMAL || dataType == SQL_NUMERIC) 
{
+                               /* MonetDB has limits for the precision and 
scale */
+                               if (columnSize > MAX_PREC || abs(decimalDigits) 
> MAX_PREC) {
+                                       /* very large precision/scale, not 
supported by MonetDB. Map this column to a string */
+                                       dataType = SQL_VARCHAR;
+                               } else {
+                                       columnSize = MIN(1, columnSize); /* 
precision must be >= 1 */
+                                       decimalDigits = MIN(0, decimalDigits); 
/* negative scales are not supported by MonetDB */
+                                       if ((int)columnSize < decimalDigits)
+                                               columnSize = decimalDigits;     
/* make precision large enough to contain all decimal digits */
+                               }
                        }
                        colmetadata[col].dataType = dataType;
                        colmetadata[col].columnSize = columnSize;
@@ -567,19 +582,18 @@ odbc_query(int caller, mvc *sql, sql_sub
                int int_val;
                lng lng_val;
 #ifdef HAVE_HGE
-// TODO                hge hge_val;    // for large decimals
+               hge hge_val;    // for decimals with precision > 18
 #endif
                flt flt_val;
                dbl dbl_val;
                DATE_STRUCT date_val;
                TIME_STRUCT time_val;
                TIMESTAMP_STRUCT ts_val;
-//             SQL_NUMERIC_STRUCT num_val;
                SQLGUID guid_val;
-               bte * blob_val = NULL;
 
                /* allocate storage for all the var sized atom types. */
                char * str_val = NULL;          // TODO: change to wchar
+               bte * blob_val = NULL;
                if (largestStringSize == 0)     // no valid string length, use 
65535 (64kB) as default
                        largestStringSize = 65535;
                if (largestStringSize > 16777215) // string length too large, 
limit to 16MB
@@ -606,6 +620,7 @@ odbc_query(int caller, mvc *sql, sql_sub
                                printf("Allocated blob_val buffer of size 
%zu\n", largestBlobSize * sizeof(bte));
                }
 
+               gdk_return gdkret = GDK_SUCCEED;
                unsigned long row = 0;
                ret = SQLFetch(stmt);   // TODO optimisation: use 
SQLExtendedFetch() to pull data array wise and use BUNappendmulti()
                while (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
@@ -655,7 +670,7 @@ odbc_query(int caller, mvc *sql, sql_sub
                                                break;
                                        case SQL_DECIMAL:
_______________________________________________
checkin-list mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to