Changeset: eacb1f23e00e for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=eacb1f23e00e
Modified Files:
        sql/backends/monet5/sql_round_impl.h
        sql/server/sql_decimal.c
        sql/server/sql_decimal.h
        sql/server/sql_parser.y
Branch: Oct2020-merged-Jun2020
Log Message:

Stricter decimal parsing.


diffs (truncated from 317 to 300 lines):

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
@@ -309,48 +309,42 @@ nil_2dec(TYPE *res, const void *val, con
 static inline str
 str_2dec_body(TYPE *res, const str val, const int d, const int sc)
 {
-       char *s = val;
-       char *dot, *end;
+       char *s;
        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)", *val, *d, *sc);
+
+       s = *val;
+
+       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]))
-               throw(SQL, STRING(TYPE), SQLSTATE(42000) "Decimal (%s) doesn't 
have format (%d.%d)", s, d, sc);
+       // 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)", *val, *d, *sc);
 
-       value = decimal_from_str(s, &end);
-       if (*s == '+' || *s == '-')
-               digits--;
-       if (scale < sc) {
+       // 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! */
+               int dff = *sc - scale;  /* CANNOT be 0! */
                if (dff >= MAX_SCALE)
-                       throw(SQL, STRING(TYPE), SQLSTATE(42000) "Rounding of 
decimal (%s) doesn't fit format (%d.%d)", s, d, sc);
+                       throw(SQL, STRING(TYPE), SQLSTATE(42000) "Rounding of 
decimal (%s) doesn't fit format (%d.%d)", *val, *d, *sc);
 
                value *= scales[dff];
                scale += dff;
                digits += dff;
-       } else if (scale > sc) {
+       } else if (scale > *sc) {
                /* the current scale is too big, decrease it by correctly 
rounding */
                /* we should round properly, and check for overflow (res >= 
10^digits+scale) */
-               int dff = scale - sc;   /* CANNOT be 0 */
+               int dff = scale - *sc;  /* CANNOT be 0 */
 
                if (dff >= MAX_SCALE)
-                       throw(SQL, STRING(TYPE), SQLSTATE(42000) "Rounding of 
decimal (%s) doesn't fit format (%d.%d)", s, d, sc);
+                       throw(SQL, STRING(TYPE), SQLSTATE(42000) "Rounding of 
decimal (%s) doesn't fit format (%d.%d)", *val, *d, *sc);
 
                BIG rnd = scales[dff] >> 1;
 
@@ -361,11 +355,13 @@ str_2dec_body(TYPE *res, const str val, 
                value /= scales[dff];
                scale -= dff;
                digits -= dff;
-               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]) {
+                       throw(SQL, STRING(TYPE), SQLSTATE(42000) "Rounding of 
decimal (%s) doesn't fit format (%d.%d)", *val, *d, *sc);
+               }
        }
-       if (value <= -scales[d] || value >= scales[d]  || *end)
-               throw(SQL, STRING(TYPE), SQLSTATE(42000) "Decimal (%s) doesn't 
have format (%d.%d)", s, d, sc);
+       if (value <= -scales[*d] || value >= scales[*d]) {
+               throw(SQL, STRING(TYPE), SQLSTATE(42000) "Decimal (%s) doesn't 
have format (%d.%d)", *val, *d, *sc);
+       }
        *res = (TYPE) value;
        return MAL_SUCCEED;
 }
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,97 @@
 
 #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++;
+               _digits++; // add one to digits for single implicit preceding 
0, e.g. (+|-)0.456
+               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.
+               dec++;
+               if (*dec == '.')
+                       goto fractional_sep_first_opp;
        }
+       for (; *dec && (isdigit((unsigned char) *dec)); dec++) {
+               if (res > max0 || (res == max0 && *dec - '0' > max1))
+                       break;
+               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))
+                       break;
+               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,43 @@ 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);
+                       int digits;
+                       int scale;
+                       int has_errors;
+                       sql_subtype t;
+
+                       DEC_TPE value = decimal_from_str(s, &digits, &scale, 
&has_errors);
+
+                       if (has_errors) {
+                               char *msg = sql_message(SQLSTATE(22003) "Double 
value too large or not a number (%s)", $1);
+
+                               yyerror(m, msg);
+                               _DELETE(msg);
                                $$ = NULL;
                                YYABORT;
                        }
-                       sql_find_subtype(&t, "double", 51, 0 );
-                       $$ = _newAtomNode(atom_float(SA, &t, val));
+
+                       if (digits <= MAX_DEC_DIGITS) {
+                               double val = strtod($1,NULL);
+                               sql_find_subtype(&t, "decimal", digits, scale );
+                               $$ = _newAtomNode( atom_dec(SA, &t, value, 
val));
+                       }
+                       else {
+                               char *p = $1;
_______________________________________________
checkin-list mailing list
checkin-list@monetdb.org
https://www.monetdb.org/mailman/listinfo/checkin-list

Reply via email to