Skip to content

Commit

Permalink
sql: Convert big integers from string
Browse files Browse the repository at this point in the history
Correctly handles the signed and unsigned integers on the way from sql
expression to VDBE.
VDBE doesn't distinguish yet signed and unsigned integers.
  • Loading branch information
stanztt committed Mar 15, 2019
1 parent 9d50d57 commit fd27028
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 125 deletions.
23 changes: 12 additions & 11 deletions src/box/sql/expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/box/sql/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
63 changes: 42 additions & 21 deletions src/box/sql/sqlInt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);

/**
Expand All @@ -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);
Expand Down
160 changes: 69 additions & 91 deletions src/box/sql/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -591,120 +591,98 @@ 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;
for (i = 2; z[i] == '0'; i++);
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;
}

/*
Expand Down
2 changes: 1 addition & 1 deletion src/box/sql/vdbe.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit fd27028

Please sign in to comment.