[tarantool-patches] [PATCH v2 01/15] sql: Convert big integers from string

Stanislav Zudin szudin at tarantool.org
Mon Apr 1 23:44:39 MSK 2019


Correctly handles the signed and unsigned integers on the way from sql
expression to VDBE.
VDBE doesn't distinguish yet signed and unsigned integers.

Part of #3810
---
 src/box/sql/expr.c   |  23 ++++---
 src/box/sql/main.c   |   2 +-
 src/box/sql/sqlInt.h |  63 +++++++++++------
 src/box/sql/util.c   | 160 +++++++++++++++++++------------------------
 src/box/sql/vdbe.c   |   2 +-
 5 files changed, 125 insertions(+), 125 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 82688dff3..3fbfab7a0 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -3335,23 +3335,24 @@ expr_code_int(struct Parse *parse, struct Expr *expr, bool is_neg,
 		int64_t value;
 		const char *z = expr->u.zToken;
 		assert(z != NULL);
-		int c = sql_dec_or_hex_to_i64(z, &value);
-		if (c == 1 || (c == 2 && !is_neg) ||
-		    (is_neg && value == SMALLEST_INT64)) {
+		enum atoi_result c = sql_dec_or_hex_to_i64(z, is_neg, &value);
+		switch(c) {
+		case ATOI_OVERFLOW:
 			if (sql_strnicmp(z, "0x", 2) == 0) {
 				sqlErrorMsg(parse,
-						"hex literal too big: %s%s",
-						is_neg ? "-" : "", z);
+					    "hex literal too big: %s%s",
+					    is_neg ? "-" : "", z);
 			} else {
 				sqlErrorMsg(parse,
-						"oversized integer: %s%s",
-						is_neg ? "-" : "", z);
+					    "oversized integer: %s%s",
+					    is_neg ? "-" : "", z);
 			}
-		} else {
-			if (is_neg)
-				value = c == 2 ? SMALLEST_INT64 : -value;
+			break;
+		case ATOI_UNSIGNED:
+		case ATOI_SIGNED:
 			sqlVdbeAddOp4Dup8(v, OP_Int64, 0, mem, 0,
-					      (u8 *)&value, P4_INT64);
+					  (u8 *)&value, P4_INT64);
+			break;
 		}
 	}
 }
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index 0b3bd201e..9fe2e2c9d 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -1920,7 +1920,7 @@ sql_uri_int64(const char *zFilename,	/* Filename as passed to xOpen */
 {
 	const char *z = sql_uri_parameter(zFilename, zParam);
 	int64_t v;
-	if (z != NULL && sql_dec_or_hex_to_i64(z, &v) == 0)
+	if (z != NULL && sql_dec_or_hex_to_i64(z, false, &v) == 0)
 		bDflt = v;
 	return bDflt;
 }
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index eb1488576..0ef373c24 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -4274,21 +4274,37 @@ enum field_type *
 field_type_sequence_dup(struct Parse *parse, enum field_type *types,
 			uint32_t len);
 
+enum atoi_result {
+	/** Successful transformation.
+	 * Fits in a 64-bit signed integer.
+	 */
+	ATOI_SIGNED = 0,
+	/** Integer is too large for a 64-bit
+	 * unsigned integer or is malformed
+	 */
+	ATOI_OVERFLOW = 1,
+	/** Successful transformation.
+	 * Fits in a 64-bit unsigned integer.
+	 */
+	ATOI_UNSIGNED = 2
+};
+
+
 /**
- * Convert z to a 64-bit signed integer.  z must be decimal. This
- * routine does *not* accept hexadecimal notation.
+ * Converts z to a 64-bit signed or unsigned integer.
+ * z must be decimal. This routine does *not* accept
+ * hexadecimal notation.
  *
  * If the z value is representable as a 64-bit twos-complement
- * integer, then write that value into *val and return 0.
+ * integer, then write that value into *val and return ATOI_SIGNED.
  *
- * If z is exactly 9223372036854775808, return 2.  This special
- * case is broken out because while 9223372036854775808 cannot be
- * a signed 64-bit integer, its negative -9223372036854775808 can
- * be.
+ * If z is a number in the range
+ * [9223372036854775808, 18446744073709551615] function returns
+ * ATOI_UNSIGNED and result must be treated as unsigned.
  *
  * If z is too big for a 64-bit integer and is not
  * 9223372036854775808  or if z contains any non-numeric text,
- * then return 1.
+ * then return ATOI_OVERFLOW.
  *
  * length is the number of bytes in the string (bytes, not
  * characters). The string is not necessarily zero-terminated.
@@ -4298,13 +4314,14 @@ field_type_sequence_dup(struct Parse *parse, enum field_type *types,
  * @param[out] val Output integer value.
  * @param length String length in bytes.
  * @retval
- *     0    Successful transformation.  Fits in a 64-bit signed
- *          integer.
- *     1    Integer too large for a 64-bit signed integer or is
- *          malformed
- *     2    Special case of 9223372036854775808
- */
-int
+ *     ATOI_SIGNED	Successful transformation.
+ *     			Fits in a 64-bit signed integer
+ *     ATOI_OVERFLOW    Integer too large for a 64-bit
+ *     			unsigned integer or is malformed
+ *     ATOI_UNSIGNED    Successful transformation.
+ *     			Fits in a 64-bit signed integer
+ */
+enum atoi_result
 sql_atoi64(const char *z, int64_t *val, int length);
 
 /**
@@ -4313,14 +4330,18 @@ sql_atoi64(const char *z, int64_t *val, int length);
  * accepts hexadecimal literals, whereas sql_atoi64() does not.
  *
  * @param z Literal being parsed.
+ * @param is_neg Sign of the number being converted
  * @param[out] val Parsed value.
  * @retval
- *     0    Successful transformation.  Fits in a 64-bit signed integer.
- *     1    Integer too large for a 64-bit signed integer or is malformed
- *     2    Special case of 9223372036854775808
- */
-int
-sql_dec_or_hex_to_i64(const char *z, int64_t *val);
+ *     ATOI_SIGNED	Successful transformation.
+ *     			Fits in a 64-bit signed integer
+ *     ATOI_OVERFLOW    Integer too large for a 64-bit
+ *     			unsigned integer or is malformed
+ *     ATOI_UNSIGNED    Successful transformation.
+ *     			Fits in a 64-bit signed integer
+ */
+enum atoi_result
+sql_dec_or_hex_to_i64(const char *z, bool is_neg, int64_t *val);
 
 void sqlErrorWithMsg(sql *, int, const char *, ...);
 void sqlError(sql *, int);
diff --git a/src/box/sql/util.c b/src/box/sql/util.c
index c89e2e8ab..be77f72f8 100644
--- a/src/box/sql/util.c
+++ b/src/box/sql/util.c
@@ -591,110 +591,56 @@ sqlAtoF(const char *z, double *pResult, int length)
 #endif				/* SQL_OMIT_FLOATING_POINT */
 }
 
-/*
- * Compare the 19-character string zNum against the text representation
- * value 2^63:  9223372036854775808.  Return negative, zero, or positive
- * if zNum is less than, equal to, or greater than the string.
- * Note that zNum must contain exactly 19 characters.
- *
- * Unlike memcmp() this routine is guaranteed to return the difference
- * in the values of the last digit if the only difference is in the
- * last digit.  So, for example,
- *
- *      compare2pow63("9223372036854775800", 1)
- *
- * will return -8.
- */
-static int
-compare2pow63(const char *zNum, int incr)
-{
-	int c = 0;
-	int i;
-	/* 012345678901234567 */
-	const char *pow63 = "922337203685477580";
-	for (i = 0; c == 0 && i < 18; i++) {
-		c = (zNum[i * incr] - pow63[i]) * 10;
-	}
-	if (c == 0) {
-		c = zNum[18 * incr] - '8';
-		testcase(c == (-1));
-		testcase(c == 0);
-		testcase(c == (+1));
-	}
-	return c;
-}
+#ifndef INT64_MIN_MOD
+/* Modulo of INT64_MIN */
+#define INT64_MIN_MOD 0x8000000000000000
+#endif
 
-int
+enum atoi_result
 sql_atoi64(const char *z, int64_t *val, int length)
 {
-	int incr = 1;
-	u64 u = 0;
 	int neg = 0;		/* assume positive */
-	int i;
-	int c = 0;
-	int nonNum = 0;		/* True if input contains UTF16 with high byte non-zero */
-	const char *zStart;
 	const char *zEnd = z + length;
-	incr = 1;
+	int incr = 1;
 	while (z < zEnd && sqlIsspace(*z))
 		z += incr;
-	if (z < zEnd) {
-		if (*z == '-') {
-			neg = 1;
-			z += incr;
-		} else if (*z == '+') {
-			z += incr;
-		}
-	}
-	zStart = z;
-	/* Skip leading zeros. */
-	while (z < zEnd && z[0] == '0') {
+
+	if (z >= zEnd)
+		return ATOI_OVERFLOW; /* invalid format */
+	if (*z == '-') {
+		neg = 1;
 		z += incr;
 	}
-	for (i = 0; &z[i] < zEnd && (c = z[i]) >= '0' && c <= '9';
-	     i += incr) {
-		u = u * 10 + c - '0';
-	}
-	if (u > LARGEST_INT64) {
-		*val = neg ? SMALLEST_INT64 : LARGEST_INT64;
-	} else if (neg) {
-		*val = -(i64) u;
+
+	char* end = NULL;
+	u64 u = strtoull(z, &end, 10);
+	if (end == z)
+		return ATOI_OVERFLOW;
+	if (errno == ERANGE)
+		return ATOI_OVERFLOW;
+
+	enum atoi_result rc;
+	if (neg) {
+		rc = ATOI_SIGNED;
+		if (u <= INT64_MAX)
+			*val = -u;
+		else if (u == INT64_MIN_MOD)
+			*val = (i64) u;
+		else
+			rc = ATOI_OVERFLOW;
 	} else {
 		*val = (i64) u;
+		rc = (u <= INT64_MAX) ? ATOI_SIGNED
+				      : ATOI_UNSIGNED;
 	}
-	if (&z[i] < zEnd || (i == 0 && zStart == z) || i > 19 * incr ||
-	    nonNum) {
-		/* zNum is empty or contains non-numeric text or is longer
-		 * than 19 digits (thus guaranteeing that it is too large)
-		 */
-		return 1;
-	} else if (i < 19 * incr) {
-		/* Less than 19 digits, so we know that it fits in 64 bits */
-		assert(u <= LARGEST_INT64);
-		return 0;
-	} else {
-		/* zNum is a 19-digit numbers.  Compare it against 9223372036854775808. */
-		c = compare2pow63(z, incr);
-		if (c < 0) {
-			/* zNum is less than 9223372036854775808 so it fits */
-			assert(u <= LARGEST_INT64);
-			return 0;
-		} else if (c > 0) {
-			/* zNum is greater than 9223372036854775808 so it overflows */
-			return 1;
-		} else {
-			/* zNum is exactly 9223372036854775808.  Fits if negative.  The
-			 * special case 2 overflow if positive
-			 */
-			assert(u - 1 == LARGEST_INT64);
-			return neg ? 0 : 2;
-		}
-	}
+
+	return rc;
 }
 
-int
-sql_dec_or_hex_to_i64(const char *z, int64_t *val)
+enum atoi_result
+sql_dec_or_hex_to_i64(const char *z, bool is_neg, int64_t *val)
 {
+	enum atoi_result rc;
 	if (z[0] == '0' && (z[1] == 'x' || z[1] == 'X')) {
 		uint64_t u = 0;
 		int i, k;
@@ -702,9 +648,41 @@ sql_dec_or_hex_to_i64(const char *z, int64_t *val)
 		for (k = i; sqlIsxdigit(z[k]); k++)
 			u = u * 16 + sqlHexToInt(z[k]);
 		memcpy(val, &u, 8);
-		return (z[k] == 0 && k - i <= 16) ? 0 : 1;
+
+		/* Determine result */
+		if ((k - i) > 16)
+			rc = ATOI_OVERFLOW;
+		else if (u > INT64_MAX)
+			rc = ATOI_UNSIGNED;
+		else
+			rc = ATOI_SIGNED;
+	}
+	else
+		rc = sql_atoi64(z, val, sqlStrlen30(z));
+
+	/* Apply sign */
+	if (is_neg) {
+		switch (rc) {
+		case ATOI_SIGNED:
+			*val = -*val;
+			break;
+		case ATOI_OVERFLOW:
+			/* n/a */
+			break;
+		case ATOI_UNSIGNED:
+			/* A special processing is required
+			 * for the INT64_MIN value. Any other
+			 * values can't be presented as signed,
+			 * so change the return value to error. */
+			if (*val == INT64_MIN)
+				rc = ATOI_SIGNED;
+			else
+				rc = ATOI_OVERFLOW;
+			break;
+		}
 	}
-	return sql_atoi64(z, val, sqlStrlen30(z));
+
+	return rc;
 }
 
 /*
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index c1da9a4aa..c87b10757 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -410,7 +410,7 @@ static u16 SQL_NOINLINE computeNumericType(Mem *pMem)
 	assert((pMem->flags & (MEM_Str|MEM_Blob))!=0);
 	if (sqlAtoF(pMem->z, &pMem->u.r, pMem->n)==0)
 		return 0;
-	if (sql_atoi64(pMem->z, (int64_t *)&pMem->u.i, pMem->n)==SQL_OK)
+	if (sql_atoi64(pMem->z, (int64_t *)&pMem->u.i, pMem->n)==ATOI_SIGNED)
 		return MEM_Int;
 	return MEM_Real;
 }
-- 
2.17.1





More information about the Tarantool-patches mailing list