[Tarantool-patches] [PATCH v1 4/4] sql: introduce decimal to arithmetic

imeevma at tarantool.org imeevma at tarantool.org
Mon Aug 16 18:57:05 MSK 2021


This patch introduces arithmetic for DECIMAL in SQL. After this patch,
DECIMAL values can participate in arithmetic along with INTEGER,
UNSIGNED, DOUBLE, and other DECIMAL values.

Part of #4415
---
 src/box/sql/mem.c             | 124 +++++++++++++++++++++++++++++-
 test/sql-tap/decimal.test.lua | 141 +++++++++++++++++++++++++++++++++-
 2 files changed, 262 insertions(+), 3 deletions(-)

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index ff8b40d7f..a4ec98f34 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1733,6 +1733,18 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
 		}
 		return -1;
 	}
+	if (mem->type == MEM_TYPE_DEC) {
+		if (decimal_is_neg(&mem->u.d)) {
+			if (decimal_to_int64(&mem->u.d, i) == NULL)
+				return -1;
+			*is_neg = *i < 0;
+			return 0;
+		}
+		if (decimal_to_uint64(&mem->u.d, (uint64_t *)i) == NULL)
+			return -1;
+		*is_neg = false;
+		return 0;
+	}
 	return -1;
 }
 
@@ -1760,6 +1772,19 @@ mem_get_uint(const struct Mem *mem, uint64_t *u)
 		}
 		return -1;
 	}
+	if (mem->type == MEM_TYPE_DEC) {
+		if (decimal_is_neg(&mem->u.d)) {
+			int64_t i;
+			if (decimal_to_int64(&mem->u.d, &i) == NULL || i < 0)
+				return -1;
+			assert(i == 0);
+			*u = 0;
+			return 0;
+		}
+		if (decimal_to_uint64(&mem->u.d, u) == NULL)
+			return -1;
+		return 0;
+	}
 	return -1;
 }
 
@@ -1778,6 +1803,10 @@ mem_get_double(const struct Mem *mem, double *d)
 		*d = (double)mem->u.u;
 		return 0;
 	}
+	if (mem->type == MEM_TYPE_DEC) {
+		*d = atof(decimal_str(&mem->u.d));
+		return 0;
+	}
 	if (mem->type == MEM_TYPE_STR) {
 		if (sqlAtoF(mem->z, d, mem->n) == 0)
 			return -1;
@@ -1786,6 +1815,34 @@ mem_get_double(const struct Mem *mem, double *d)
 	return -1;
 }
 
+int
+mem_get_dec(const struct Mem *mem, decimal_t *d)
+{
+	if (mem->type == MEM_TYPE_DOUBLE) {
+		if (decimal_from_double(d, mem->u.r) == NULL)
+			return -1;
+		return 0;
+	}
+	if (mem->type == MEM_TYPE_INT) {
+		decimal_from_int64(d, mem->u.r);
+		return 0;
+	}
+	if (mem->type == MEM_TYPE_UINT) {
+		decimal_from_int64(d, mem->u.u);
+		return 0;
+	}
+	if (mem->type == MEM_TYPE_DEC) {
+		*d = mem->u.d;
+		return 0;
+	}
+	if (mem->type == MEM_TYPE_STR) {
+		if (decimal_from_string(d, tt_cstr(mem->z, mem->n)) == NULL)
+			return -1;
+		return 0;
+	}
+	return -1;
+}
+
 int
 mem_get_bool(const struct Mem *mem, bool *b)
 {
@@ -1946,12 +2003,12 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 static inline int
 check_types_numeric_arithmetic(const struct Mem *a, const struct Mem *b)
 {
-	if (!mem_is_num(a) || mem_is_metatype(a) || a->type == MEM_TYPE_DEC) {
+	if (!mem_is_num(a) || mem_is_metatype(a)) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(a),
 			 "integer, unsigned or double");
 		return -1;
 	}
-	if (!mem_is_num(b) || mem_is_metatype(b) || b->type == MEM_TYPE_DEC) {
+	if (!mem_is_num(b) || mem_is_metatype(b)) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(b),
 			 "integer, unsigned or double");
 		return -1;
@@ -1976,6 +2033,20 @@ mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		mem_set_double(result, a + b);
 		return 0;
 	}
+	if (((left->type | right->type) & MEM_TYPE_DEC) != 0) {
+		decimal_t a;
+		decimal_t b;
+		decimal_t res;
+		mem_get_dec(left, &a);
+		mem_get_dec(right, &b);
+		if (decimal_add(&res, &a, &b) == NULL) {
+			diag_set(ClientError, ER_SQL_EXECUTE,
+				 "decimal is overflowed");
+			return -1;
+		}
+		mem_set_dec(result, &res);
+		return 0;
+	}
 	int64_t res;
 	bool is_neg;
 	if (sql_add_int(left->u.i, left->type == MEM_TYPE_INT, right->u.i,
@@ -2004,6 +2075,20 @@ mem_sub(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		mem_set_double(result, a - b);
 		return 0;
 	}
+	if (((left->type | right->type) & MEM_TYPE_DEC) != 0) {
+		decimal_t a;
+		decimal_t b;
+		decimal_t res;
+		mem_get_dec(left, &a);
+		mem_get_dec(right, &b);
+		if (decimal_sub(&res, &a, &b) == NULL) {
+			diag_set(ClientError, ER_SQL_EXECUTE,
+				 "decimal is overflowed");
+			return -1;
+		}
+		mem_set_dec(result, &res);
+		return 0;
+	}
 	int64_t res;
 	bool is_neg;
 	if (sql_sub_int(left->u.i, left->type == MEM_TYPE_INT, right->u.i,
@@ -2032,6 +2117,20 @@ mem_mul(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		mem_set_double(result, a * b);
 		return 0;
 	}
+	if (((left->type | right->type) & MEM_TYPE_DEC) != 0) {
+		decimal_t a;
+		decimal_t b;
+		decimal_t res;
+		mem_get_dec(left, &a);
+		mem_get_dec(right, &b);
+		if (decimal_mul(&res, &a, &b) == NULL) {
+			diag_set(ClientError, ER_SQL_EXECUTE,
+				 "decimal is overflowed");
+			return -1;
+		}
+		mem_set_dec(result, &res);
+		return 0;
+	}
 	int64_t res;
 	bool is_neg;
 	if (sql_mul_int(left->u.i, left->type == MEM_TYPE_INT, right->u.i,
@@ -2065,6 +2164,27 @@ mem_div(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		mem_set_double(result, a / b);
 		return 0;
 	}
+	if (((left->type | right->type) & MEM_TYPE_DEC) != 0) {
+		decimal_t a;
+		decimal_t b;
+		decimal_t zero;
+		decimal_t res;
+		mem_get_dec(left, &a);
+		mem_get_dec(right, &b);
+		decimal_zero(&zero);
+		if (decimal_compare(&b, &zero) == 0) {
+			diag_set(ClientError, ER_SQL_EXECUTE,
+				 "division by zero");
+			return -1;
+		}
+		if (decimal_div(&res, &a, &b) == NULL) {
+			diag_set(ClientError, ER_SQL_EXECUTE,
+				 "decimal is overflowed");
+			return -1;
+		}
+		mem_set_dec(result, &res);
+		return 0;
+	}
 	if (right->u.u == 0) {
 		diag_set(ClientError, ER_SQL_EXECUTE, "division by zero");
 		return -1;
diff --git a/test/sql-tap/decimal.test.lua b/test/sql-tap/decimal.test.lua
index 3f1c285cf..3c6215728 100755
--- a/test/sql-tap/decimal.test.lua
+++ b/test/sql-tap/decimal.test.lua
@@ -3,7 +3,7 @@ local build_path = os.getenv("BUILDDIR")
 package.cpath = build_path..'/test/sql-tap/?.so;'..build_path..'/test/sql-tap/?.dylib;'..package.cpath
 
 local test = require("sqltester")
-test:plan(84)
+test:plan(101)
 
 local dec = require("decimal")
 local dec1 = dec.new("111")
@@ -789,6 +789,145 @@ test:do_execsql_test(
         1000
     })
 
+-- Check that arithmetic operations work with UUIDs as intended.
+test:do_execsql_test(
+    "dec-14.1.1",
+    [[
+        SELECT -u FROM t2;
+    ]], {
+        dec.new(-111), dec.new(-3333), dec.new(-55555)
+    })
+
+test:do_execsql_test(
+    "dec-14.1.2",
+    [[
+        SELECT u + 0 FROM t2;
+    ]], {
+        dec1, dec3, dec2
+    })
+
+test:do_execsql_test(
+    "dec-14.1.3",
+    [[
+        SELECT u - 0.5 FROM t2;
+    ]], {
+        110.5, 3332.5, 55554.5
+    })
+
+test:do_execsql_test(
+    "dec-14.1.4",
+    [[
+        SELECT u * 1 FROM t2;
+    ]], {
+        dec1, dec3, dec2
+    })
+
+test:do_execsql_test(
+    "dec-14.1.5",
+    [[
+        SELECT u / 1.0 FROM t2;
+    ]], {
+        111, 3333, 55555
+    })
+
+test:do_catchsql_test(
+    "dec-14.1.6",
+    [[
+        SELECT u % 1 FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to integer"
+    })
+
+-- Check that bitwise operations work with UUIDs as intended.
+test:do_catchsql_test(
+    "dec-14.2.1",
+    [[
+        SELECT ~u FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to unsigned"
+    })
+
+test:do_catchsql_test(
+    "dec-14.2.2",
+    [[
+        SELECT u >> 1 FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to unsigned"
+    })
+
+test:do_catchsql_test(
+    "dec-14.2.3",
+    [[
+        SELECT u << 1 FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to unsigned"
+    })
+
+test:do_catchsql_test(
+    "dec-14.2.4",
+    [[
+        SELECT u | 1 FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to unsigned"
+    })
+
+test:do_catchsql_test(
+    "dec-14.2.5",
+    [[
+        SELECT u & 1 FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to unsigned"
+    })
+
+-- Check that logical operations work with UUIDs as intended.
+test:do_catchsql_test(
+    "dec-14.3.1",
+    [[
+        SELECT NOT u FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to boolean"
+    })
+
+test:do_catchsql_test(
+    "dec-14.3.2",
+    [[
+        SELECT u AND true FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to boolean"
+    })
+
+test:do_catchsql_test(
+    "dec-14.3.3",
+    [[
+        SELECT u OR true FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to boolean"
+    })
+
+test:do_catchsql_test(
+    "dec-14.3.4",
+    [[
+        SELECT true AND u FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to boolean"
+    })
+
+test:do_catchsql_test(
+    "dec-14.3.5",
+    [[
+        SELECT true OR u FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert decimal(111) to boolean"
+    })
+
+test:do_catchsql_test(
+    "dec-15",
+    [[
+        SELECT u || u from t2;
+    ]], {
+        1, "Inconsistent types: expected string or varbinary got decimal(111)"
+    })
+
 test:execsql([[
     DROP TRIGGER t;
     DROP VIEW v;
-- 
2.25.1



More information about the Tarantool-patches mailing list