[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