Skip to content

Configurable floats format on conversions #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 16 additions & 65 deletions driver/connect.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,71 +81,6 @@
/* 25 */
#define TYPE_IVL_MINUTE_TO_SECOND "INTERVAL_MINUTE_TO_SECOND"

/*
* ES-to-C-SQL mappings.
* DATA_TYPE(SYS TYPES) : SQL_<type> -> SQL_C_<type>
* Intervals not covered, since C==SQL, with no ES customization.
*/
/* -6: SQL_TINYINT -> SQL_C_TINYINT */
#define ES_BYTE_TO_CSQL SQL_C_TINYINT
#define ES_BYTE_TO_SQL SQL_TINYINT
/* 5: SQL_SMALLINT -> SQL_C_SHORT */
#define ES_SHORT_TO_CSQL SQL_C_SSHORT
#define ES_SHORT_TO_SQL SQL_SMALLINT
/* 4: SQL_INTEGER -> SQL_C_LONG */
#define ES_INTEGER_TO_CSQL SQL_C_SLONG
#define ES_INTEGER_TO_SQL SQL_INTEGER
/* -5: SQL_BIGINT -> SQL_C_SBIGINT */
#define ES_LONG_TO_CSQL SQL_C_SBIGINT
#define ES_LONG_TO_SQL SQL_BIGINT
/* 6: SQL_FLOAT -> SQL_C_DOUBLE */
#define ES_HALF_TO_CSQL_FLOAT SQL_C_DOUBLE
#define ES_HALF_TO_SQL_FLOAT SQL_FLOAT
/* 6: SQL_FLOAT -> SQL_C_DOUBLE */
#define ES_SCALED_TO_CSQL_FLOAT SQL_C_DOUBLE
#define ES_SCALED_TO_SQL_FLOAT SQL_FLOAT
/* 7: SQL_REAL -> SQL_C_DOUBLE */
#define ES_FLOAT_TO_CSQL SQL_C_FLOAT
#define ES_FLOAT_TO_SQL SQL_REAL
/* 8: SQL_DOUBLE -> SQL_C_FLOAT */
#define ES_DOUBLE_TO_CSQL SQL_C_DOUBLE
#define ES_DOUBLE_TO_SQL SQL_DOUBLE
/* 16: ??? -> SQL_C_TINYINT */
#define ES_BOOLEAN_TO_CSQL SQL_C_BIT
#define ES_BOOLEAN_TO_SQL SQL_BIT
/* 12: SQL_VARCHAR -> SQL_C_WCHAR */
#define ES_KEYWORD_TO_CSQL SQL_C_WCHAR /* XXX: CBOR needs _CHAR */
#define ES_KEYWORD_TO_SQL SQL_VARCHAR
/* 12: SQL_VARCHAR -> SQL_C_WCHAR */
#define ES_TEXT_TO_CSQL SQL_C_WCHAR /* XXX: CBOR needs _CHAR */
#define ES_TEXT_TO_SQL SQL_VARCHAR
/* 12: SQL_VARCHAR -> SQL_C_WCHAR */
#define ES_IP_TO_CSQL SQL_C_WCHAR /* XXX: CBOR needs _CHAR */
#define ES_IP_TO_SQL SQL_VARCHAR
/* 91: SQL_TYPE_DATE -> SQL_C_TYPE_DATE */
#define ES_DATE_TO_CSQL SQL_C_TYPE_DATE
#define ES_DATE_TO_SQL SQL_TYPE_DATE
/* 93: SQL_TYPE_TIMESTAMP -> SQL_C_TYPE_TIMESTAMP */
#define ES_DATETIME_TO_CSQL SQL_C_TYPE_TIMESTAMP
#define ES_DATETIME_TO_SQL SQL_TYPE_TIMESTAMP
/* -3: SQL_VARBINARY -> SQL_C_BINARY */
#define ES_BINARY_TO_CSQL SQL_C_BINARY
#define ES_BINARY_TO_SQL SQL_VARBINARY
/* 0: SQL_TYPE_NULL -> SQL_C_TINYINT */
#define ES_NULL_TO_CSQL SQL_C_STINYINT // ???
#define ES_NULL_TO_SQL SQL_TYPE_NULL
/*
* ES-non mappable
*/
/* 1111: ??? -> SQL_C_BINARY */
#define ES_UNSUPPORTED_TO_CSQL SQL_C_BINARY
#define ES_UNSUPPORTED_TO_SQL ESODBC_SQL_UNSUPPORTED
/* 2002: ??? -> SQL_C_BINARY */
#define ES_OBJECT_TO_CSQL SQL_C_BINARY
#define ES_OBJECT_TO_SQL ESODBC_SQL_OBJECT
/* 2002: ??? -> SQL_C_BINARY */
#define ES_NESTED_TO_CSQL SQL_C_BINARY
#define ES_NESTED_TO_SQL ESODBC_SQL_NESTED


/* structure for one row returned by the ES.
Expand Down Expand Up @@ -1305,6 +1240,22 @@ SQLRETURN config_dbc(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs)
dbc->apply_tz = wstr2bool(&attrs->apply_tz);
INFOH(dbc, "apply TZ: %s.", dbc->apply_tz ? "true" : "false");

/* how to print the floats? */
assert(1 <= attrs->sci_floats.cnt); /* default should apply */
if ((char)attrs->sci_floats.str[0] == ESODBC_DSN_FLTS_DEF[0]) {
dbc->sci_floats = ESODBC_FLTS_DEFAULT;
} else if ((char)attrs->sci_floats.str[0] == ESODBC_DSN_FLTS_SCI[0]) {
dbc->sci_floats = ESODBC_FLTS_SCIENTIFIC;
} else if ((char)attrs->sci_floats.str[0] == ESODBC_DSN_FLTS_AUTO[0]) {
dbc->sci_floats = ESODBC_FLTS_AUTO;
} else {
ERRH(dbc, "unknown floats representation '" LWPDL "'.",
LWSTR(&attrs->sci_floats));
SET_HDIAG(dbc, SQL_STATE_HY000,
"invalid floats representation setting", 0);
goto err;
}

/*
* Version checking mode
*/
Expand Down
68 changes: 68 additions & 0 deletions driver/connect.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,74 @@
#include "handles.h"
#include "dsn.h"


/*
* ES-to-C-SQL mappings.
* DATA_TYPE(SYS TYPES) : SQL_<type> -> SQL_C_<type>
* Intervals not covered, since C==SQL, with no ES customization.
*/
/* -6: SQL_TINYINT -> SQL_C_TINYINT */
#define ES_BYTE_TO_CSQL SQL_C_TINYINT
#define ES_BYTE_TO_SQL SQL_TINYINT
/* 5: SQL_SMALLINT -> SQL_C_SHORT */
#define ES_SHORT_TO_CSQL SQL_C_SSHORT
#define ES_SHORT_TO_SQL SQL_SMALLINT
/* 4: SQL_INTEGER -> SQL_C_LONG */
#define ES_INTEGER_TO_CSQL SQL_C_SLONG
#define ES_INTEGER_TO_SQL SQL_INTEGER
/* -5: SQL_BIGINT -> SQL_C_SBIGINT */
#define ES_LONG_TO_CSQL SQL_C_SBIGINT
#define ES_LONG_TO_SQL SQL_BIGINT
/* 6: SQL_FLOAT -> SQL_C_DOUBLE */
#define ES_HALF_TO_CSQL_FLOAT SQL_C_DOUBLE
#define ES_HALF_TO_SQL_FLOAT SQL_FLOAT
/* 6: SQL_FLOAT -> SQL_C_DOUBLE */
#define ES_SCALED_TO_CSQL_FLOAT SQL_C_DOUBLE
#define ES_SCALED_TO_SQL_FLOAT SQL_FLOAT
/* 7: SQL_REAL -> SQL_C_DOUBLE */
#define ES_FLOAT_TO_CSQL SQL_C_FLOAT
#define ES_FLOAT_TO_SQL SQL_REAL
/* 8: SQL_DOUBLE -> SQL_C_FLOAT */
#define ES_DOUBLE_TO_CSQL SQL_C_DOUBLE
#define ES_DOUBLE_TO_SQL SQL_DOUBLE
/* 16: ??? -> SQL_C_TINYINT */
#define ES_BOOLEAN_TO_CSQL SQL_C_BIT
#define ES_BOOLEAN_TO_SQL SQL_BIT
/* 12: SQL_VARCHAR -> SQL_C_WCHAR */
#define ES_KEYWORD_TO_CSQL SQL_C_WCHAR /* XXX: CBOR needs _CHAR */
#define ES_KEYWORD_TO_SQL SQL_VARCHAR
/* 12: SQL_VARCHAR -> SQL_C_WCHAR */
#define ES_TEXT_TO_CSQL SQL_C_WCHAR /* XXX: CBOR needs _CHAR */
#define ES_TEXT_TO_SQL SQL_VARCHAR
/* 12: SQL_VARCHAR -> SQL_C_WCHAR */
#define ES_IP_TO_CSQL SQL_C_WCHAR /* XXX: CBOR needs _CHAR */
#define ES_IP_TO_SQL SQL_VARCHAR
/* 91: SQL_TYPE_DATE -> SQL_C_TYPE_DATE */
#define ES_DATE_TO_CSQL SQL_C_TYPE_DATE
#define ES_DATE_TO_SQL SQL_TYPE_DATE
/* 93: SQL_TYPE_TIMESTAMP -> SQL_C_TYPE_TIMESTAMP */
#define ES_DATETIME_TO_CSQL SQL_C_TYPE_TIMESTAMP
#define ES_DATETIME_TO_SQL SQL_TYPE_TIMESTAMP
/* -3: SQL_VARBINARY -> SQL_C_BINARY */
#define ES_BINARY_TO_CSQL SQL_C_BINARY
#define ES_BINARY_TO_SQL SQL_VARBINARY
/* 0: SQL_TYPE_NULL -> SQL_C_TINYINT */
#define ES_NULL_TO_CSQL SQL_C_STINYINT // ???
#define ES_NULL_TO_SQL SQL_TYPE_NULL
/*
* ES-non mappable
*/
/* 1111: ??? -> SQL_C_BINARY */
#define ES_UNSUPPORTED_TO_CSQL SQL_C_BINARY
#define ES_UNSUPPORTED_TO_SQL ESODBC_SQL_UNSUPPORTED
/* 2002: ??? -> SQL_C_BINARY */
#define ES_OBJECT_TO_CSQL SQL_C_BINARY
#define ES_OBJECT_TO_SQL ESODBC_SQL_OBJECT
/* 2002: ??? -> SQL_C_BINARY */
#define ES_NESTED_TO_CSQL SQL_C_BINARY
#define ES_NESTED_TO_SQL ESODBC_SQL_NESTED


BOOL connect_init();
void connect_cleanup();

Expand Down
177 changes: 118 additions & 59 deletions driver/convert.c
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ static inline void gd_offset_update(esodbc_stmt_st *stmt, size_t cnt,



/* transfer to the application a 0-terminated (but unaccounted for) cstr_st */
/* transfer to the application a 0-terminated (but unaccounted for) xstr_st */
static SQLRETURN transfer_xstr0(esodbc_rec_st *arec, esodbc_rec_st *irec,
xstr_st *xsrc, void *data_ptr, SQLLEN *octet_len_ptr)
{
Expand Down Expand Up @@ -1125,7 +1125,8 @@ SQLRETURN sql2c_longlong(esodbc_rec_st *arec, esodbc_rec_st *irec,
default:
BUGH(stmt, "unexpected unhanlded data type: %d.",
get_rec_c_type(arec, irec));
return SQL_ERROR;
RET_HDIAG(stmt, SQL_STATE_HY000, "Unexpected application data "
"type for source of type long", 0);
}
DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied long long: %lld.", arec,
data_ptr, ll);
Expand Down Expand Up @@ -1204,74 +1205,130 @@ static SQLRETURN double_to_binary(esodbc_rec_st *arec, esodbc_rec_st *irec,
return SQL_SUCCESS;
}

/*
* TODO!!!
* 1. use default precision
* 2. config for scientific notation.
* 3. sprintf (for now)
*/
static SQLRETURN double_to_str(esodbc_rec_st *arec, esodbc_rec_st *irec,
double dbl, void *data_ptr, SQLLEN *octet_len_ptr, BOOL wide)
static inline const void *floats_rep(esodbc_stmt_st *stmt, double dbl,
BOOL wide, int *rep)
{
long long whole;
unsigned long long fraction;
double rest;
SQLSMALLINT scale;
size_t pos, octets;
/* buffer unit size */
size_t usize = wide ? sizeof(SQLWCHAR) : sizeof(SQLCHAR);
esodbc_state_et state = SQL_STATE_00000;
esodbc_stmt_st *stmt = arec->desc->hdr.stmt;
/* buffer is overprovisioned for !wide, but avoids double declaration */
SQLCHAR buff[(2 * ESODBC_PRECISION_INT64 + /*.*/1 + /*\0*/1)
* sizeof(SQLWCHAR)];
xstr_st xstr = (xstr_st) {
.wide = wide,
.c = (cstr_st) {
/* same vals for both wide and C strings */
.str = buff,
.cnt = 0
}
};
static const char *flt_def = "%.*f";
static const wchar_t *flt_wdef = L"%.*f";
static const char *flt_exp = "%.*E";
static const wchar_t *flt_wexp = L"%.*E";

/*
* split the whole and fractional parts
*/
assert(sizeof(dbl) == sizeof(whole)); /* [double]==[long long] */
whole = (long long)dbl;
rest = dbl - whole;

/* retain user defined or data source default number of fraction digits */
scale = 0 < arec->scale ? arec->scale : irec->es_type->maximum_scale;
rest *= pow10(scale);
rest = round(rest);
fraction = rest < 0 ? (unsigned long long) -rest
: (unsigned long long)rest;

/* copy integer part into work buffer */
pos = i64tot((int64_t)whole, buff, wide);
/* would writing just the whole part + \0 fit into the buffer? */
octets = buff_octet_size((pos + 1) * usize, usize, arec, irec, &state);
if (state) {
REJECT_AS_OOR(stmt, dbl, /*fixed?*/FALSE, "[STRING]<[floating.whole]");
esodbc_dbc_st *dbc = HDRH(stmt)->dbc;
double abs;

if (wide) {
switch (dbc->sci_floats) {
case ESODBC_FLTS_DEFAULT:
*rep = ESODBC_FLTS_DEFAULT;
return flt_wdef;
case ESODBC_FLTS_SCIENTIFIC:
*rep = ESODBC_FLTS_SCIENTIFIC;
return flt_wexp;
case ESODBC_FLTS_AUTO:
abs = (0 <= dbl) ? dbl : -dbl;
if (abs < 1E-3 || 1E7 <= abs) {
*rep = ESODBC_FLTS_SCIENTIFIC;
return flt_wexp;
} else {
*rep = ESODBC_FLTS_DEFAULT;
return flt_wdef;
}
default:
BUGH(stmt, "unexpected floats representation value: %d.",
dbc->sci_floats);
*rep = ESODBC_FLTS_DEFAULT;
return flt_wdef;
}
} else {
assert(octets == (pos + 1) * usize);
switch (dbc->sci_floats) {
case ESODBC_FLTS_DEFAULT:
*rep = ESODBC_FLTS_DEFAULT;
return flt_def;
case ESODBC_FLTS_SCIENTIFIC:
*rep = ESODBC_FLTS_SCIENTIFIC;
return flt_exp;
case ESODBC_FLTS_AUTO:
abs = (0 <= dbl) ? dbl : -dbl;
if (abs < 1E-3 || 1E7 <= abs) {
*rep = ESODBC_FLTS_SCIENTIFIC;
return flt_exp;
} else {
*rep = ESODBC_FLTS_DEFAULT;
return flt_def;
}
default:
BUGH(stmt, "unexpected floats representation value: %d.",
dbc->sci_floats);
*rep = ESODBC_FLTS_DEFAULT;
return flt_def;
}
}
}

static SQLRETURN double_to_str(esodbc_rec_st *arec, esodbc_rec_st *irec,
double dbl, void *data_ptr, SQLLEN *octet_len_ptr, BOOL wide)
{
# define DBL_BASE10_MAX_LEN /*-0.*/3 + DBL_DIG - DBL_MIN_10_EXP
esodbc_stmt_st *stmt = HDRH(arec->desc)->stmt;
SQLCHAR buff[DBL_BASE10_MAX_LEN + /*\0*/1];
SQLWCHAR wbuff[DBL_BASE10_MAX_LEN + /*\0*/1];
size_t usize;
esodbc_state_et state;
xstr_st xstr;
SQLSMALLINT prec; /* ~ision */
const char *flt_fmt;
const wchar_t *flt_wfmt;
int rep;
size_t octets, cnt;
int n;

/* Note: there's no way for the app to ask for a number of decimal digits
* - =scale - from the driver in the conversion (setting the scale of the
* ARD would apply to the C type, _[W]CHAR) except for limiting the buffer
* size, which has an uneven effect, due to the variance in the number of
* the whole part of the floats. */
/* https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/rules-for-conversions */
prec = irec->es_type->maximum_scale;
if (wide) {
((SQLWCHAR *)buff)[pos ++] = L'.';
flt_wfmt = (const wchar_t *)floats_rep(stmt, dbl, /*wide?*/TRUE, &rep);
/* Note: this call would fail on Win CRT on insufficient buffer */
n = swprintf(wbuff, DBL_BASE10_MAX_LEN + 1,flt_wfmt, prec, dbl);
xstr.w.str = wbuff;
} else {
((SQLCHAR *)buff)[pos ++] = '.';
flt_fmt = (const char *)floats_rep(stmt, dbl, /*wide?*/FALSE, &rep);
n = snprintf(buff, DBL_BASE10_MAX_LEN + 1, flt_fmt, prec, dbl);
xstr.c.str = buff;
}
if (n <= 0) {
/* ..if this will work this time. */
ERRNH(stmt, "failed to %c-print double %lf and precision %d.",
dbl, prec, wide ? 'W' : 'C');
RET_HDIAG(stmt, SQL_STATE_HY000, "failed to print double", 0);
}
xstr.c.cnt = (size_t)n;
assert(xstr.c.cnt == xstr.w.cnt);
xstr.wide = wide;

/* copy fractional part into work buffer */
pos += ui64tot((uint64_t)fraction, (char *)buff + pos * usize, wide);

xstr.w.cnt = pos; /*==.c.cnt*/
usize = wide ? sizeof(SQLWCHAR) : sizeof(SQLCHAR);
state = SQL_STATE_00000;
octets = buff_octet_size((n + 1) * usize, usize, arec, irec, &state);
if (state) {
cnt = octets / usize;
/* fail the call if exponential printing or default with truncation of
* whole part */
if (rep == ESODBC_FLTS_SCIENTIFIC ||
cnt < xstr.c.cnt - (prec + /* radix char ('.')*/!!prec)) {
REJECT_AS_OOR(stmt, dbl, FALSE, "[STRING]<[floating.whole]");
}
} else {
assert(octets == (n + 1) * usize);
}

return transfer_xstr0(arec, irec, &xstr, data_ptr, octet_len_ptr);
# undef DBL_BASE10_MAX_LEN
}


SQLRETURN sql2c_double(esodbc_rec_st *arec, esodbc_rec_st *irec,
SQLULEN pos, double dbl)
{
Expand Down Expand Up @@ -1377,7 +1434,8 @@ SQLRETURN sql2c_double(esodbc_rec_st *arec, esodbc_rec_st *irec,
default:
BUGH(stmt, "unexpected unhanlded data type: %d.",
get_rec_c_type(arec, irec));
return SQL_ERROR;
RET_HDIAG(stmt, SQL_STATE_HY000, "Unexpected application data "
"type for source of type double", 0);
}

DBGH(stmt, "REC@0x%p, data_ptr@0x%p, copied double: %.6e.", arec,
Expand Down Expand Up @@ -3180,7 +3238,8 @@ SQLRETURN sql2c_string(esodbc_rec_st *arec, esodbc_rec_st *irec,

default:
BUGH(stmt, "unexpected unhandled data type: %d.", ctarget);
return SQL_ERROR;
RET_HDIAG(stmt, SQL_STATE_HY000, "Unexpected application data "
"type for source of type string", 0);
}

return SQL_SUCCESS;
Expand Down
Loading