Changeset: 8d706ac54502 for MonetDB URL: https://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=8d706ac54502 Modified Files: sql/server/rel_optimizer.c Branch: Oct2020 Log Message:
Import decimal fixes from June. diffs (truncated from 562 to 300 lines): diff --git a/sql/backends/monet5/sql_result.c b/sql/backends/monet5/sql_result.c --- a/sql/backends/monet5/sql_result.c +++ b/sql/backends/monet5/sql_result.c @@ -1888,6 +1888,8 @@ get_print_width(int mtype, sql_class ecl count = 1 + digits; if (scale > 0) count += 1; + if (scale == digits) // for preceding 0, e.g. 0. + count += 1; return count; } else if (eclass == EC_DATE) { return 10; diff --git a/sql/backends/monet5/sql_round_impl.h b/sql/backends/monet5/sql_round_impl.h --- a/sql/backends/monet5/sql_round_impl.h +++ b/sql/backends/monet5/sql_round_impl.h @@ -310,31 +310,23 @@ static inline str str_2dec_body(TYPE *res, const str val, const int d, const int sc) { char *s = val; - char *dot, *end; int digits; int scale; BIG value; - dot = strchr(s, '.'); - if (dot != NULL) { - s = strip_extra_zeros(s); - digits = _strlen(s) - 1; - scale = _strlen(dot + 1); - } else { - digits = _strlen(s); - scale = 0; - } - end = NULL; + if (d < 0 || d >= (int) (sizeof(scales) / sizeof(scales[0]))) + throw(SQL, STRING(TYPE), SQLSTATE(42000) "Decimal (%s) doesn't have format (%d.%d)", s, d, sc); + + int has_errors; value = 0; - if (digits < 0) - throw(SQL, STRING(TYPE), SQLSTATE(42000) "Decimal (%s) doesn't have format (%d.%d)", s, d, sc); - if (d < 0 || (size_t) d >= sizeof(scales) / sizeof(scales[0])) + // s = strip_extra_zeros(s); + + value = decimal_from_str(s, &digits, &scale, &has_errors); + if (has_errors) throw(SQL, STRING(TYPE), SQLSTATE(42000) "Decimal (%s) doesn't have format (%d.%d)", s, d, sc); - value = decimal_from_str(s, &end); - if (*s == '+' || *s == '-') - digits--; + // handle situations where the de facto scale is different from the formal scale. if (scale < sc) { /* the current scale is too small, increase it by adding 0's */ int dff = sc - scale; /* CANNOT be 0! */ @@ -364,7 +356,7 @@ str_2dec_body(TYPE *res, const str val, if (value >= scales[d] || value <= -scales[d]) throw(SQL, STRING(TYPE), SQLSTATE(42000) "Rounding of decimal (%s) doesn't fit format (%d.%d)", s, d, sc); } - if (value <= -scales[d] || value >= scales[d] || *end) + if (value <= -scales[d] || value >= scales[d]) throw(SQL, STRING(TYPE), SQLSTATE(42000) "Decimal (%s) doesn't have format (%d.%d)", s, d, sc); *res = (TYPE) value; return MAL_SUCCEED; diff --git a/sql/server/rel_optimizer.c b/sql/server/rel_optimizer.c --- a/sql/server/rel_optimizer.c +++ b/sql/server/rel_optimizer.c @@ -6406,7 +6406,7 @@ rel_push_project_up(visitor *v, sql_rel Check if they can be pushed up, ie are they not changing or introducing any columns used by the upper operator. */ - + exps = new_exp_list(v->sql->sa); for (n = l->exps->h; n; n = n->next) { sql_exp *e = n->data; diff --git a/sql/server/sql_decimal.c b/sql/server/sql_decimal.c --- a/sql/server/sql_decimal.c +++ b/sql/server/sql_decimal.c @@ -10,46 +10,103 @@ #include "sql_decimal.h" -#ifdef HAVE_HGE -hge -#else -lng -#endif -decimal_from_str(char *dec, char **end) + +DEC_TPE +decimal_from_str(char *dec, int* digits, int* scale, int* has_errors) { + #ifdef HAVE_HGE - hge res = 0; - const hge max0 = GDK_hge_max / 10, max1 = GDK_hge_max % 10; + const hge max0 = GDK_hge_max / 10, max1 = GDK_hge_max % 10; #else - lng res = 0; - const lng max0 = GDK_lng_max / 10, max1 = GDK_lng_max % 10; + const lng max0 = GDK_lng_max / 10, max1 = GDK_lng_max % 10; #endif - int neg = 0, seen_dot = 0; + + assert(digits); + assert(scale); + assert(has_errors); + DEC_TPE res = 0; + *has_errors = 0; + + int _digits = 0; + int _scale = 0; + +// preceding whitespace: + int neg = 0; while(isspace((unsigned char) *dec)) dec++; + +// optional sign: if (*dec == '-') { neg = 1; dec++; } else if (*dec == '+') { dec++; } - for (; *dec && (isdigit((unsigned char) *dec) || *dec == '.'); dec++) { - if (*dec != '.') { - if (res > max0 || (res == max0 && *dec - '0' > max1)) - break; - res *= 10; - res += *dec - '0'; - } else if (seen_dot) { - break; /* dot cannot appear twice */ - } else { - seen_dot = 1; + +// optional fractional separator first opportunity + if (*dec == '.') { // case: (+|-).456 +fractional_sep_first_opp: + dec++; + goto trailing_digits; + } + +// preceding_digits: + if (!isdigit((unsigned char) *dec)) { + *has_errors = 1; + goto end_state; + } + while (*dec == '0'){ + // skip leading zeros in preceding digits, e.g. '0004563.1234' => '4563.1234' + dec++; + if (*dec == '.') { + _digits = 1; // case: 0.xyz the zero. the single preceding zero counts for one digit by convention. + goto fractional_sep_first_opp; } } + for (; *dec && (isdigit((unsigned char) *dec)); dec++) { + if (res > max0 || (res == max0 && *dec - '0' > max1)) { + *has_errors = 1; + return 0; + } + res *= 10; + res += *dec - '0'; + _digits++; + } + +// optional fractional separator second opportunity + if (*dec == '.') // case: (+|-)123.(456) + dec++; + else // case: (+|-)123 + goto trailing_whitespace; + +trailing_digits: + if (!isdigit((unsigned char) *dec)) + goto trailing_whitespace; + for (; *dec && (isdigit((unsigned char) *dec)); dec++) { + if (res > max0 || (res == max0 && *dec - '0' > max1)) { + *has_errors = 1; + return 0; + } + res *= 10; + res += *dec - '0'; + _scale++; + } + _digits += _scale; + +trailing_whitespace: while(isspace((unsigned char) *dec)) dec++; - if (end) - *end = dec; + +end_state: + /* When the string cannot be parsed up to and including the null terminator, + * the string is an invalid decimal representation. */ + if (*dec != 0) + *has_errors = 1; + + *digits = _digits; + *scale = _scale; + if (neg) return -res; else diff --git a/sql/server/sql_decimal.h b/sql/server/sql_decimal.h --- a/sql/server/sql_decimal.h +++ b/sql/server/sql_decimal.h @@ -14,19 +14,14 @@ #include "gdk.h" #ifdef HAVE_HGE -extern hge decimal_from_str(char *dec, char **end); -extern char * decimal_to_str(sql_allocator *sa, hge v, sql_subtype *t); +#define DEC_TPE hge #else -extern lng decimal_from_str(char *dec, char **end); -extern char * decimal_to_str(sql_allocator *sa, lng v, sql_subtype *t); +#define DEC_TPE lng #endif -#ifdef HAVE_HGE -extern hge -#else -extern lng -#endif -scale2value(int scale); +extern DEC_TPE decimal_from_str(char *dec, int* digits, int* scale, int* has_errors); +extern char * decimal_to_str(sql_allocator *sa, DEC_TPE v, sql_subtype *t); +DEC_TPE scale2value(int scale); #endif /* _SQL_DECIMAL_H */ diff --git a/sql/server/sql_parser.y b/sql/server/sql_parser.y --- a/sql/server/sql_parser.y +++ b/sql/server/sql_parser.y @@ -4710,37 +4710,36 @@ literal: } | INTNUM { char *s = sa_strdup(SA, $1); - char *dot = strchr(s, '.'); - int digits = _strlen(s) - 1; - int scale = digits - (int) (dot-s); - sql_subtype t; - - if (digits <= 0) - digits = 1; - if (digits <= MAX_DEC_DIGITS) { -#ifdef HAVE_HGE - hge value = decimal_from_str(s, NULL); -#else - lng value = decimal_from_str(s, NULL); -#endif - - if (*s == '+' || *s == '-') - digits --; - sql_find_subtype(&t, "decimal", digits, scale ); - $$ = _newAtomNode( atom_dec(SA, &t, value)); - } else { - char *p = $1; - double val; - - errno = 0; - val = strtod($1,&p); - if (p == $1 || is_dbl_nil(val) || (errno == ERANGE && (val < -1 || val > 1))) { - sqlformaterror(m, SQLSTATE(22003) "Double value too large or not a number (%s)", $1); - $$ = NULL; - YYABORT; + int digits; + int scale; + int has_errors; + sql_subtype t; + + DEC_TPE value = decimal_from_str(s, &digits, &scale, &has_errors); + + if (!has_errors && digits <= MAX_DEC_DIGITS) { + // The float-like value seems to fit in decimal storage + sql_find_subtype(&t, "decimal", digits, scale ); + $$ = _newAtomNode( atom_dec(SA, &t, value)); } - sql_find_subtype(&t, "double", 51, 0 ); - $$ = _newAtomNode(atom_float(SA, &t, val)); + else { + /* + * The float-like value either doesn't fit in integer decimal storage + * or it is not a valid float representation. + */ + char *p = $1; + double val; + + errno = 0; + val = strtod($1,&p); + if (p == $1 || is_dbl_nil(val) || (errno == ERANGE && (val < -1 || val > 1))) { + sqlformaterror(m, SQLSTATE(22003) "Double value too large or not a number (%s)", $1); + $$ = NULL; + YYABORT; + } else { + sql_find_subtype(&t, "double", 51, 0 ); _______________________________________________ checkin-list mailing list checkin-list@monetdb.org https://www.monetdb.org/mailman/listinfo/checkin-list