in driver/convert.c [2108:2372]
static SQLRETURN parse_interval_iso8601(esodbc_rec_st *rec,
SQLSMALLINT ctype, wstr_st *wstr, SQL_INTERVAL_STRUCT *ivl)
{
esodbc_stmt_st *stmt = rec->desc->hdr.stmt;
char sign;
SQLWCHAR *crr, *end;
wstr_st nr;
SQLUINTEGER uint, fraction;
BOOL has_fraction;
enum {ST_UNINITED, ST_PERIOD, ST_TIME, ST_NUMBER} state, saved;
uint16_t fields_bm; /* read fields bit mask */
static const uint16_t type2bm[] = {
1 << SQL_IS_YEAR,
1 << SQL_IS_MONTH,
1 << SQL_IS_DAY,
1 << SQL_IS_HOUR,
1 << SQL_IS_MINUTE,
1 << SQL_IS_SECOND,
(1 << SQL_IS_YEAR) | (1 << SQL_IS_MONTH),
(1 << SQL_IS_DAY) | (1 << SQL_IS_HOUR),
(1 << SQL_IS_DAY) | (1 << SQL_IS_HOUR) | (1 << SQL_IS_MINUTE),
(1 << SQL_IS_DAY) | (1 << SQL_IS_HOUR) | (1 << SQL_IS_MINUTE) |
(1 << SQL_IS_SECOND),
(1 << SQL_IS_HOUR) | (1 << SQL_IS_MINUTE),
(1 << SQL_IS_HOUR) | (1 << SQL_IS_MINUTE) | (1 << SQL_IS_SECOND),
(1 << SQL_IS_MINUTE) | (1 << SQL_IS_SECOND),
};
uint16_t type2bm_ivl; /* the type bit mask for the interval */
SQLRETURN ret;
SQLUINTEGER secs; /* ~ond~ */
/* Sets a bit in a bitmask corresponding to one interval field, given
* `_ivl`, or errs if already set.
* Uses local vars: fields_bm.
* Jumps to err_format on error. */
# define SET_BITMASK_OR_ERR(_ivl) \
do { \
uint8_t _bit = 1 << (_ivl); \
if (_bit & fields_bm) { \
ERRH(stmt, "field %d already set.", (_ivl)); \
goto err_format; \
} \
fields_bm |= _bit; \
} while (0)
/* Assigns a value to an interval field, `_ivl_field` of type `_ivl_type`,
* if current state is ST_NUMBER and the previous one matches the given
* `_prev_state`.
* Uses local vars: ivl, fields_bm, state, saved.
* Jumps to err_format on error. */
# define ASSIGN_FIELD(_prev_state, _ivl_type, _ivl_field_) \
do { \
if (state != ST_NUMBER || saved != _prev_state) { \
goto err_format; \
} \
if (_ivl_type != SQL_IS_SECOND && has_fraction) { \
goto err_format; \
} \
SET_BITMASK_OR_ERR(_ivl_type); \
ivl->intval._ivl_field_ = uint; \
DBGH(stmt, "field %d assigned value %lu.", _ivl_type, uint); \
state = saved; \
} while (0)
/* Safe ulong addition */
# define ULONG_SAFE_ADD(_to, _from) \
do { \
if (ULONG_MAX - (_from) < _to) { \
goto err_overflow; \
} else { \
_to += _from; \
} \
} while (0)
/* the interval type will be used as bitmask indexes */
assert(0 < SQL_IS_YEAR && SQL_IS_MINUTE_TO_SECOND < 16);
DBGH(stmt, "ISO 8601 to parse: [%zu] `" LWPDL "`, C target: %hd.",
wstr->cnt, LWSTR(wstr), ctype);
memset(ivl, 0, sizeof(*ivl));
sign = 0;
fields_bm = 0;
fraction = 0;
state = saved = ST_UNINITED;
for (crr = wstr->str, end = wstr->str + wstr->cnt; crr < end; ) {
switch (*crr | 0x20) { /* ~tolower(), ascii vals only */
case L'p':
if (state != ST_UNINITED) {
goto err_parse;
}
state = ST_PERIOD;
break;
case L't':
if (state != ST_PERIOD) {
goto err_parse;
}
state = ST_TIME;
break;
case L'+':
case L'-':
case L'.':
/* L'0' .. L'9' */
case L'0':
case L'1':
case L'2':
case L'3':
case L'4':
case L'5':
case L'6':
case L'7':
case L'8':
case L'9':
if (state != ST_PERIOD && state != ST_TIME) {
goto err_parse;
}
nr.str = crr;
nr.cnt = end - crr;
ret = parse_iso8601_number(rec, &nr, &uint, &sign,
&fraction, &has_fraction);
if (! SQL_SUCCEEDED(ret)) {
goto err_format;
} else {
crr = nr.str;
}
saved = state;
state = ST_NUMBER;
continue;
case L'y':
ASSIGN_FIELD(ST_PERIOD, SQL_IS_YEAR, year_month.year);
break;
case L'm':
if (state != ST_NUMBER ||
(saved != ST_PERIOD && saved != ST_TIME)) {
goto err_format;
}
if (has_fraction) {
goto err_format;
}
if (saved == ST_PERIOD) {
SET_BITMASK_OR_ERR(SQL_IS_MONTH);
ivl->intval.year_month.month = uint;
DBGH(stmt, "field %d assigned value %lu.", SQL_IS_MONTH,
uint);
} else {
SET_BITMASK_OR_ERR(SQL_IS_MINUTE);
ivl->intval.day_second.minute = uint;
DBGH(stmt, "field %d assigned value %lu.", SQL_IS_MONTH,
uint);
}
state = saved;
break;
case L'd':
ASSIGN_FIELD(ST_PERIOD, SQL_IS_DAY, day_second.day);
break;
case L'h':
ASSIGN_FIELD(ST_TIME, SQL_IS_HOUR, day_second.hour);
break;
case L's':
ASSIGN_FIELD(ST_TIME, SQL_IS_SECOND, day_second.second);
if (has_fraction) {
ivl->intval.day_second.fraction = fraction;
DBGH(stmt, "field fraction assigned value %lu.", fraction);
}
break;
default:
goto err_parse;
}
crr ++;
}
if (state != ST_PERIOD && state != ST_TIME) {
goto err_format;
}
ivl->interval_sign = (sign < 0) ? SQL_TRUE : SQL_FALSE;
assert(SQL_CODE_YEAR == SQL_IS_YEAR);
ivl->interval_type = ctype - (SQL_INTERVAL_YEAR - SQL_CODE_YEAR);
assert(0 < /*starts at 1*/ ivl->interval_type &&
ivl->interval_type < 8 * sizeof(type2bm)/sizeof(type2bm[0]));
type2bm_ivl = type2bm[ivl->interval_type - 1];
/* If the expression set fields not directly relevant to the interval AND
* the interval is not of type year/-/month one (which does seem to
* conform to the standard), rebalance the member values as expected by
* the interval type. */
if ((type2bm_ivl != fields_bm) && ((type2bm_ivl &
((1 << SQL_CODE_YEAR) | (1 << SQL_CODE_MONTH))) == 0)) {
secs = ivl->intval.day_second.second;
ULONG_SAFE_ADD(secs, 60 * ivl->intval.day_second.minute); //...
ULONG_SAFE_ADD(secs, 3600 * ivl->intval.day_second.hour);
ULONG_SAFE_ADD(secs, 24 * 3600 * ivl->intval.day_second.day);
/* clear everything set, but reinstate any set fractions */
fields_bm = 0;
memset(&ivl->intval.day_second, 0, sizeof(ivl->intval.day_second));
ivl->intval.day_second.fraction = fraction;
if (type2bm_ivl & (1 << SQL_CODE_SECOND)) {
ivl->intval.day_second.second = secs;
fields_bm |= (1 << SQL_CODE_SECOND);
} else if (has_fraction) {
/* fraction val itself is truncated away due to precision
* zero/null for intervals with no seconds component */
ERRH(stmt, "fraction in interval with no second component");
goto err_format;
}
if (type2bm_ivl & (1 << SQL_CODE_MINUTE)) {
if (ivl->intval.day_second.second) {
ivl->intval.day_second.minute =
ivl->intval.day_second.second / 60;
ivl->intval.day_second.second = secs % 60;
} else {
ivl->intval.day_second.minute = secs / 60;
assert(secs % 60 == 0);
}
fields_bm |= (1 << SQL_CODE_MINUTE);
}
if (type2bm_ivl & (1 << SQL_CODE_HOUR)) {
if (ivl->intval.day_second.minute) {
ivl->intval.day_second.hour =
ivl->intval.day_second.minute / 60;
ivl->intval.day_second.minute %= 60;
} else {
ivl->intval.day_second.hour = secs / 3600;
assert(secs % 3600 == 0);
}
fields_bm |= (1 << SQL_CODE_HOUR);
}
if (type2bm_ivl & (1 << SQL_CODE_DAY)) {
if (ivl->intval.day_second.hour) {
ivl->intval.day_second.day =
ivl->intval.day_second.hour / 24;
ivl->intval.day_second.hour %= 24;
} else {
ivl->intval.day_second.day = secs / (24 * 3600);
assert(secs % (24 * 3600) == 0);
}
fields_bm |= (1 << SQL_CODE_DAY);
}
}
/* Check that the ISO value has no fields set other than those allowed
* for the advertised type. Since the year_month and day_second form a
* union, this can't be done by checks against field values. */
if (~type2bm_ivl & fields_bm) {
ERRH(stmt, "illegal fields (0x%hx) for interval type %hd (0x%hx).",
fields_bm, ctype, type2bm_ivl);
goto err_format;
}
return ret;
err_overflow:
ERRH(stmt, "integer overflow while normalizing ISO8601 format [%zu] `"
LWPDL "`.", wstr->cnt, LWSTR(wstr));
RET_HDIAGS(stmt, SQL_STATE_22015);
err_parse:
ERRH(stmt, "unexpected current char `%c` in state %d.", *crr, state);
err_format:
ERRH(stmt, "invalid ISO8601 format [%zu] `" LWPDL "`.", wstr->cnt,
LWSTR(wstr));
RET_HDIAG(stmt, SQL_STATE_22018, "Invalid server answer", 0);
# undef ASSIGN_FIELD
# undef SET_BITMASK_OR_ERR
# undef ULONG_SAFE_ADD
}