diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index bf952c53b659eaf70872e5df75e1b558f9ef5e38..d31597e5db62f7e164b64d1892871c64c0db8fdd 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1224,7 +1224,8 @@ sqlExprAssignVarNumber(Parse * pParse, Expr * pExpr, u32 n)
 			 * variable number
 			 */
 			int64_t i;
-			bool is_ok = 0 == sql_atoi64(&z[1], &i, n - 1);
+			bool is_neg;
+			bool is_ok = 0 == sql_atoi64(&z[1], &i, &is_neg, n - 1);
 			x = (ynVar) i;
 			testcase(i == 0);
 			testcase(i == 1);
@@ -3305,29 +3306,43 @@ expr_code_int(struct Parse *parse, struct Expr *expr, bool is_neg,
 		if (is_neg)
 			i = -i;
 		sqlVdbeAddOp2(v, OP_Integer, i, mem);
+		return;
+	}
+	int64_t value;
+	const char *z = expr->u.zToken;
+	assert(z != NULL);
+	const char *sign = is_neg ? "-" : "";
+	if (z[0] == '0' && (z[1] == 'x' || z[1] == 'X')) {
+		errno = 0;
+		if (is_neg) {
+			value = strtoll(z, NULL, 16);
+		} else {
+			value = strtoull(z, NULL, 16);
+			if (value > INT64_MAX)
+				goto int_overflow;
+		}
+		if (errno != 0) {
+			diag_set(ClientError, ER_HEX_LITERAL_MAX, sign, z,
+				 strlen(z) - 2, 16);
+			parse->is_aborted = true;
+			return;
+		}
 	} else {
-		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)) {
-			const char *sign = is_neg ? "-" : "";
-			if (sql_strnicmp(z, "0x", 2) == 0) {
-				diag_set(ClientError, ER_HEX_LITERAL_MAX, sign,
-					 z, strlen(z) - 2, 16);
-			} else {
-				diag_set(ClientError, ER_INT_LITERAL_MAX, sign,
-					 z, INT64_MIN, INT64_MAX);
-			}
+		size_t len = strlen(z);
+		bool unused;
+		if (sql_atoi64(z, &value, &unused, len) != 0 ||
+		    (is_neg && (uint64_t) value > (uint64_t) INT64_MAX + 1) ||
+		    (!is_neg && (uint64_t) value > INT64_MAX)) {
+int_overflow:
+			diag_set(ClientError, ER_INT_LITERAL_MAX, sign, z,
+				 INT64_MIN, INT64_MAX);
 			parse->is_aborted = true;
-		} else {
-			if (is_neg)
-				value = c == 2 ? SMALLEST_INT64 : -value;
-			sqlVdbeAddOp4Dup8(v, OP_Int64, 0, mem, 0,
-					      (u8 *)&value, P4_INT64);
+			return;
 		}
 	}
+	if (is_neg)
+		value = -value;
+	sqlVdbeAddOp4Dup8(v, OP_Int64, 0, mem, 0, (u8 *)&value, P4_INT64);
 }
 
 /*
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 4f5bad2879f2229d06c5b95c104028a427fd48e2..34f5c74affc5d04aa964eda6c561038787ed3fbc 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -3996,52 +3996,25 @@ field_type_sequence_dup(struct Parse *parse, enum field_type *types,
 			uint32_t len);
 
 /**
- * Convert z to a 64-bit signed 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.
- *
- * 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 too big for a 64-bit integer and is not
- * 9223372036854775808  or if z contains any non-numeric text,
- * then return 1.
+ * Convert z to a 64-bit signed or unsigned integer.
+ * z must be decimal. This routine does *not* accept
+ * hexadecimal notation. Under the hood it calls
+ * strtoll or strtoull depending on presence of '-' char.
  *
  * length is the number of bytes in the string (bytes, not
  * characters). The string is not necessarily zero-terminated.
- * The encoding is given by enc.
  *
  * @param z String being parsed.
  * @param[out] val Output integer value.
+ * @param[out] is_neg Sign of the result.
  * @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
-sql_atoi64(const char *z, int64_t *val, int length);
-
-/**
- * Transform a UTF-8 integer literal, in either decimal or
- * hexadecimal, into a 64-bit signed integer.  This routine
- * accepts hexadecimal literals, whereas sql_atoi64() does not.
- *
- * @param z Literal being parsed.
- * @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
+ * @retval 0 Successful transformation. Fits in a 64-bit signed
+ *           or unsigned integer.
+ * @retval -1 Error during parsing: it contains non-digit
+ *            characters or it doesn't fit into 64-bit int.
  */
 int
-sql_dec_or_hex_to_i64(const char *z, int64_t *val);
+sql_atoi64(const char *z, int64_t *val, bool *is_neg, int length);
 
 void *sqlHexToBlob(sql *, const char *z, int n);
 u8 sqlHexToInt(int h);
diff --git a/src/box/sql/util.c b/src/box/sql/util.c
index 92f6f0fa920cfcba1763b3c0ff4244b80accae46..ce4e3a6c6357540d3bccc8620af13ef3deda343a 100644
--- a/src/box/sql/util.c
+++ b/src/box/sql/util.c
@@ -38,6 +38,7 @@
  */
 #include "sqlInt.h"
 #include <stdarg.h>
+#include <ctype.h>
 #include <math.h>
 #include "coll/coll.h"
 #include <unicode/ucasemap.h>
@@ -455,120 +456,38 @@ sqlAtoF(const char *z, double *pResult, int length)
 	return z == zEnd && nDigits > 0 && eValid && nonNum == 0;
 }
 
-/*
- * 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;
-}
-
 int
-sql_atoi64(const char *z, int64_t *val, int length)
+sql_atoi64(const char *z, int64_t *val, bool *is_neg, 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;
-	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') {
-		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;
+	*is_neg = false;
+	const char *str_end = z + length;
+	for (; z < str_end && isspace(*z); z++);
+	if (z >= str_end)
+		return -1;
+	if (*z == '-')
+		*is_neg = true;
+
+	char *end = NULL;
+	errno = 0;
+	if (*z == '-') {
+		*is_neg = true;
+		*val = strtoll(z, &end, 10);
 	} else {
-		*val = (i64) u;
+		*is_neg = false;
+		uint64_t u_val = strtoull(z, &end, 10);
+		if (u_val > (uint64_t) INT64_MAX + 1)
+			return -1;
+		*val = u_val;
 	}
-	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;
-		}
+	/* Overflow and underflow errors. */
+	if (errno != 0)
+		return -1;
+	for (; *end != 0; ++end) {
+		if (!isspace(*end))
+			return -1;
 	}
-}
 
-int
-sql_dec_or_hex_to_i64(const char *z, int64_t *val)
-{
-	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;
-	}
-	return sql_atoi64(z, val, sqlStrlen30(z));
+	return 0;
 }
 
 /*
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 6a4a303b9c2b9dd86a773c8132321e883ad5f42b..7a880d42fdcaef46e2745fe913e61ab908f14e6c 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -263,7 +263,8 @@ mem_apply_numeric_type(struct Mem *record)
 	if ((record->flags & (MEM_Str | MEM_Int | MEM_Real)) != MEM_Str)
 		return -1;
 	int64_t integer_value;
-	if (sql_atoi64(record->z, &integer_value, record->n) == 0) {
+	bool is_neg;
+	if (sql_atoi64(record->z, &integer_value, &is_neg, record->n) == 0) {
 		record->u.i = integer_value;
 		MemSetTypeFlag(record, MEM_Int);
 		return 0;
@@ -368,7 +369,8 @@ 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) == 0)
+	bool is_neg;
+	if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, &is_neg, pMem->n) == 0)
 		return MEM_Int;
 	return MEM_Real;
 }
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index f52035b0ee0c714736ac4c98785f5551ad8b6145..147b3b4ffec31a7bebce87245cbff78230be8f03 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -460,7 +460,8 @@ sqlVdbeIntValue(Mem * pMem, int64_t *i)
 		return doubleToInt64(pMem->u.r, i);
 	} else if (flags & (MEM_Str)) {
 		assert(pMem->z || pMem->n == 0);
-		if (sql_atoi64(pMem->z, (int64_t *)i, pMem->n) == 0)
+		bool is_neg;
+		if (sql_atoi64(pMem->z, i, &is_neg, pMem->n) == 0)
 			return 0;
 	}
 	return -1;
@@ -580,7 +581,9 @@ sqlVdbeMemNumerify(Mem * pMem)
 {
 	if ((pMem->flags & (MEM_Int | MEM_Real | MEM_Null)) == 0) {
 		assert((pMem->flags & (MEM_Blob | MEM_Str)) != 0);
-		if (0 == sql_atoi64(pMem->z, (int64_t *)&pMem->u.i, pMem->n)) {
+		bool is_neg;
+		if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, &is_neg,
+			       pMem->n) == 0) {
 			MemSetTypeFlag(pMem, MEM_Int);
 		} else {
 			double v;
@@ -645,7 +648,9 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
 	if (pMem->flags & MEM_Null)
 		return 0;
 	if ((pMem->flags & MEM_Blob) != 0 && type == FIELD_TYPE_NUMBER) {
-		if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, pMem->n) == 0) {
+		bool is_neg;
+		if (sql_atoi64(pMem->z,  (int64_t *) &pMem->u.i, &is_neg,
+			       pMem->n) == 0) {
 			MemSetTypeFlag(pMem, MEM_Real);
 			pMem->u.r = pMem->u.i;
 			return 0;
@@ -676,8 +681,9 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
 		return -1;
 	case FIELD_TYPE_INTEGER:
 		if ((pMem->flags & MEM_Blob) != 0) {
-			if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i,
-				       pMem->n) != 0)
+			bool is_neg;
+			if (sql_atoi64(pMem->z,  (int64_t *) &pMem->u.i,
+				       &is_neg, pMem->n) != 0)
 				return -1;
 			MemSetTypeFlag(pMem, MEM_Int);
 			return 0;
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index 2770ec29559c7f349ae715eb08ed1e32f426b352..0f14759cf533a0ef1d500e4e14506ecd5f12ce5b 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -912,25 +912,25 @@ test:do_execsql_test(
         -- </func-8.6>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-8.7",
     [[
         SELECT typeof(sum(x)) FROM (SELECT '9223372036' || '854775808' AS x
                             UNION ALL SELECT -9223372036854775807)
     ]], {
         -- <func-8.7>
-        "number"
+        1, "Failed to execute SQL statement: integer overflow"
         -- </func-8.7>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-8.8",
     [[
         SELECT sum(x)>0.0 FROM (SELECT '9223372036' || '854775808' AS x
                             UNION ALL SELECT -9223372036850000000)
     ]], {
         -- <func-8.8>
-        true
+        1, "Failed to execute SQL statement: integer overflow"
         -- </func-8.8>
     })
 
diff --git a/test/sql/integer-overflow.result b/test/sql/integer-overflow.result
index d6ca66ec989454cd09162b8f05ce57c3372abc51..a4944d1a042a10b67da2782b038eb43d2a8a74b5 100644
--- a/test/sql/integer-overflow.result
+++ b/test/sql/integer-overflow.result
@@ -48,7 +48,11 @@ box.execute('SELECT 9223372036854775808 - 1;')
 --
 box.execute('SELECT CAST(\'9223372036854775808\' AS INTEGER);')
 ---
-- error: 'Type mismatch: can not convert 9223372036854775808 to integer'
+- metadata:
+  - name: CAST('9223372036854775808' AS INTEGER)
+    type: integer
+  rows:
+  - [-9223372036854775808]
 ...
 -- Due to inexact represantation of large integers in terms of
 -- floating point numbers, numerics with value < INT64_MAX