TRAFODION-1710 setString() method and numerics with scale The setString() method did not accept strings with digits after the decimal point, like setString(1, "123.45").
Project: http://git-wip-us.apache.org/repos/asf/incubator-trafodion/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafodion/commit/a4caa9ac Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafodion/tree/a4caa9ac Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafodion/diff/a4caa9ac Branch: refs/heads/master Commit: a4caa9ac4afe331358a74b211784a08779ca298e Parents: a2786b5 Author: Hans Zeller <[email protected]> Authored: Tue May 10 01:49:18 2016 +0000 Committer: Hans Zeller <[email protected]> Committed: Tue May 10 01:49:18 2016 +0000 ---------------------------------------------------------------------- core/sql/sqludr/sqludr.cpp | 107 +++++++++++++++++-- .../java/org/trafodion/sql/udr/TupleInfo.java | 86 +++++++++++++-- .../java/org/trafodion/sql/udr/TypeInfo.java | 6 ++ 3 files changed, 181 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-trafodion/blob/a4caa9ac/core/sql/sqludr/sqludr.cpp ---------------------------------------------------------------------- diff --git a/core/sql/sqludr/sqludr.cpp b/core/sql/sqludr/sqludr.cpp index f1741b9..3ea6ad6 100644 --- a/core/sql/sqludr/sqludr.cpp +++ b/core/sql/sqludr/sqludr.cpp @@ -696,6 +696,10 @@ TypeInfo::TypeInfo(SQLTypeCode sqlType, // length is the length in characters, but d_.length_ is // the byte length, multiply by min bytes per char d_.length_ = length * minBytesPerChar(); + if (d_.length_ < 0) + throw UDRException(38900, + "Length of a character type must not be negative, got %d", + d_.length_); if (d_.collation_ == UNDEFINED_COLLATION) throw UDRException(38900,"Collation must be specified for CHAR type in TypeInfo::TypeInfo"); break; @@ -711,6 +715,10 @@ TypeInfo::TypeInfo(SQLTypeCode sqlType, if (d_.length_ > 32767) // see also CharType::CharType in ../common/CharType.cpp d_.flags_ |= TYPE_FLAG_4_BYTE_VC_LEN; + if (d_.length_ < 0) + throw UDRException(38900, + "Length of a varchar type must not be negative, got %d", + d_.length_); break; case CLOB: @@ -2012,13 +2020,16 @@ void TypeInfo::setString(const char *val, int stringLen, char *row) const case DECIMAL_UNSIGNED: { char buf[200]; - long lval; - double dval; + long lval = 0; + double dval = 0.0; int numCharsConsumed = 0; int rc = 0; + int vScale = 0; + bool sawMinusDot = false; // ignore trailing blanks - while (val[stringLen-1] == ' ') + while (val[stringLen-1] == ' ' || + val[stringLen-1] == '\t') stringLen--; if (stringLen+1 > sizeof(buf)) @@ -2032,15 +2043,89 @@ void TypeInfo::setString(const char *val, int stringLen, char *row) const buf[stringLen] = 0; if (isApproxNumeric) - rc = sscanf(buf,"%lf%n", &dval, &numCharsConsumed) < 0; + rc = sscanf(buf,"%lf%n", &dval, &numCharsConsumed); else - rc = sscanf(buf,"%ld%n", &lval, &numCharsConsumed) < 0; + rc = sscanf(buf,"%ld%n", &lval, &numCharsConsumed); - if (rc < 0) - throw UDRException( - 38900, - "Error in setString(), \"%s\" is not a numeric value", - buf); + if (rc <= 0) + { + bool isOK = false; + + // could not read a long or float value, this could be an error + // or a number that starts with '.' or '-.' with optional + // leading white space. Check for this special case before + // raising an exception. + if (!isApproxNumeric && d_.scale_ > 0 && numCharsConsumed == 0) + { + while (buf[numCharsConsumed] == ' ' || + buf[numCharsConsumed] == '\t') + numCharsConsumed++; + if (buf[numCharsConsumed] == '-' && + buf[numCharsConsumed+1] == '.') + { + // the number starts with "-.", remember + // to negate it at the end and go on + sawMinusDot = true; + numCharsConsumed++; // skip over the '-' + isOK = true; + } + else if (buf[numCharsConsumed] == '.') + { + // the number starts with '.', that's + // ok, continue on + isOK = true; + } + } + + if (!isOK) + throw UDRException( + 38900, + "Error in setString(), \"%s\" is not a numeric value", + buf); + } + + if (d_.scale_ > 0 && !isApproxNumeric) + { + if (buf[numCharsConsumed] == '.') + { + int sign = (lval < 0 ? -1 : 1); + + // skip over the decimal dot + numCharsConsumed++; + + // process digits following the dot + while (numCharsConsumed < stringLen && + buf[numCharsConsumed] >= '0' && + buf[numCharsConsumed] <= '9' && + lval <= LONG_MAX/10) + { + lval = 10*lval + + ((int)(buf[numCharsConsumed++] - '0')) * sign; + vScale++; + } + } + + if (sawMinusDot) + lval = -lval; + + while (vScale < d_.scale_) + if (lval <= LONG_MAX/10) + { + lval *= 10; + vScale++; + } + else + throw UDRException( + 38900, + "Error in setString(): Value %s exceeds range for a long", + buf); + + if (vScale > d_.scale_) + throw UDRException( + 38900, + "Error in setString(): Value %s exceeds scale %d", + buf, d_.scale_); + } // check for any non-white space left after conversion while (numCharsConsumed < stringLen) @@ -2048,7 +2133,7 @@ void TypeInfo::setString(const char *val, int stringLen, char *row) const buf[numCharsConsumed] != '\t') throw UDRException( 38900, - "Found non-numeric character in setString for a numeric column: %s", + "Error in setString(): \"%s\" is not a valid, in-range numeric value", buf); else numCharsConsumed++; http://git-wip-us.apache.org/repos/asf/incubator-trafodion/blob/a4caa9ac/core/sql/src/main/java/org/trafodion/sql/udr/TupleInfo.java ---------------------------------------------------------------------- diff --git a/core/sql/src/main/java/org/trafodion/sql/udr/TupleInfo.java b/core/sql/src/main/java/org/trafodion/sql/udr/TupleInfo.java index e118f44..65dac20 100644 --- a/core/sql/src/main/java/org/trafodion/sql/udr/TupleInfo.java +++ b/core/sql/src/main/java/org/trafodion/sql/udr/TupleInfo.java @@ -1326,13 +1326,85 @@ public class TupleInfo extends TMUDRSerializableObject { case NUMERIC_UNSIGNED: case DECIMAL_UNSIGNED: long lval; - try {lval = Long.parseLong(val);} - catch (Exception e1) { - throw new UDRException( - 38900, - "Error in setString() \"%s\" is not a long value", - val); - } + int scale = t.getScale(); + + if (scale == 0) + { + try {lval = Long.parseLong(val);} + catch (Exception e1) { + throw new UDRException( + 38900, + "Error in setString() \"%s\" is not a long value", + val); + } + } + else + { + // setLong wants a long value that is scaled up by "scale" + // e.g. 12.34 with a column of scale 3 would become 12340. + boolean negate = false; + String tval = val.trim(); + // scale of value read + int vScale = 0; + + if (tval.charAt(0) == '-') + { + negate = true; + tval = tval.substring(1,tval.length()).trim(); + } + + try { + // position of decimal dot or decimal digit after the dot + int ddPos = tval.indexOf('.'); + int len = tval.length(); + + if (ddPos < 0) + // no dot is the same as a trailing dot + ddPos = len; + + if (ddPos > 0) + // read the number before the (optional) decimal point + lval = Long.parseLong(tval.substring(0, ddPos)); + else + // the number starts with a dot + lval = 0; + + // read any digits after the decimal point + if (++ddPos < len) + { + long fraction = Long.parseLong(tval.substring(ddPos, len)); + vScale = (len - ddPos); + + for (int s=0; s<vScale; s++) + lval *= 10; + + lval += fraction; + } + + if (negate) + lval = -lval; + + // Now we got the value in lval with a scale of vScale. + // Scale it up to "scale" + while (vScale < scale) + { + lval *= 10; + vScale++; + } + + } catch (Exception e2) { + throw new UDRException( + 38900, + "Error in setString(): \"%s\" is not an in-range numeric value", + val); + } + + if (vScale > scale) + throw new UDRException( + 38900, + "Error in setString(): Scale of value %s exceeds that of the column, %d", + val, scale); + } setLong(colNum, lval); break; http://git-wip-us.apache.org/repos/asf/incubator-trafodion/blob/a4caa9ac/core/sql/src/main/java/org/trafodion/sql/udr/TypeInfo.java ---------------------------------------------------------------------- diff --git a/core/sql/src/main/java/org/trafodion/sql/udr/TypeInfo.java b/core/sql/src/main/java/org/trafodion/sql/udr/TypeInfo.java index e42d0b2..de7fdc6 100644 --- a/core/sql/src/main/java/org/trafodion/sql/udr/TypeInfo.java +++ b/core/sql/src/main/java/org/trafodion/sql/udr/TypeInfo.java @@ -334,6 +334,9 @@ public class TypeInfo extends TMUDRSerializableObject { // length is the length in characters, but length_ is // the byte length, multiply by min bytes per char length_ = length * minBytesPerChar(); + if (length_ < 0) + throw new UDRException(38900,"Length of a character type must not be negative, got %d", + length_); if (collation_ == SQLCollationCode.UNDEFINED_COLLATION.ordinal()) throw new UDRException(38900,"Collation must be specified for CHAR type in TypeInfo::TypeInfo"); break; @@ -349,6 +352,9 @@ public class TypeInfo extends TMUDRSerializableObject { if (length_ > 32767) // see also CharType::CharType in ../common/CharType.cpp flags_ |= TYPE_FLAG_4_BYTE_VC_LEN; + if (length_ < 0) + throw new UDRException(38900,"Length of a varchar type must not be negative, got %d", + length_); break; case CLOB:
