Skip to content

Commit 0636ec4

Browse files
ImeevMAkyukhin
authored andcommitted
sql: arithmetic for DATETIME and INTERVAL
After this patch, it will be possible to perform permitted arithmetic operations on DATETIME and INTERVAL values. Closes tarantool#6773 @TarantoolBot document Title: Field type interval is available in SQL The INTERVAL field type is now available in SQL. INTERVAL values cannot be compared. INTERVAL values can be explicitly and implicitly cast to ANY. There are currently no literals for the INTERVAL field type.
1 parent e61f4f6 commit 0636ec4

14 files changed

+393
-88
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## feature/sql
2+
3+
* Field type INTERVAL is introduced to SQL (gh-6773).

src/box/sql/mem.c

+144-9
Original file line numberDiff line numberDiff line change
@@ -2002,15 +2002,20 @@ check_types_numeric_arithmetic(const struct Mem *a, const struct Mem *b)
20022002
return 0;
20032003
}
20042004

2005-
int
2006-
mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
2005+
/**
2006+
* Add the first MEM to the second MEM and write the result to the third MEM.
2007+
* The first and the second MEMs should be of numeric types. The result is of
2008+
* numeric type.
2009+
*/
2010+
static int
2011+
mem_add_num(const struct Mem *left, const struct Mem *right, struct Mem *result)
20072012
{
2008-
if (mem_is_any_null(left, right)) {
2009-
mem_set_null(result);
2010-
return 0;
2011-
}
2012-
if (check_types_numeric_arithmetic(left, right) != 0)
2013+
assert(mem_is_num(left) && !mem_is_metatype(left));
2014+
if (!mem_is_num(right) || mem_is_metatype(right)) {
2015+
diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(right),
2016+
"integer, decimal or double");
20132017
return -1;
2018+
}
20142019
if (((left->type | right->type) & MEM_TYPE_DOUBLE) != 0) {
20152020
double a;
20162021
double b;
@@ -2044,15 +2049,79 @@ mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
20442049
return 0;
20452050
}
20462051

2052+
/**
2053+
* Add the first MEM to the second MEM and write the result to the third MEM.
2054+
* The first MEM should be of DATETIME type and the second MEMs should be of
2055+
* INTERVAL type. The result is of DATETIME type.
2056+
*/
2057+
static int
2058+
mem_add_dt(const struct Mem *left, const struct Mem *right, struct Mem *result)
2059+
{
2060+
assert(mem_is_datetime(left) && !mem_is_metatype(left));
2061+
if (!mem_is_interval(right) || mem_is_metatype(right)) {
2062+
diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(right),
2063+
"interval");
2064+
return -1;
2065+
}
2066+
mem_set_datetime(result, &left->u.dt);
2067+
return datetime_increment_by(&result->u.dt, 1, &right->u.itv);
2068+
}
2069+
2070+
/**
2071+
* Add the first MEM to the second MEM and write the result to the third MEM.
2072+
* The first MEM should be of INTERVAL type and the second MEMs should be of
2073+
* INTERVAL or DATETIME type. The result is of the same type as the second
2074+
* argument.
2075+
*/
2076+
static int
2077+
mem_add_itv(const struct Mem *left, const struct Mem *right, struct Mem *result)
2078+
{
2079+
assert(mem_is_interval(left) && !mem_is_metatype(left));
2080+
if (mem_is_datetime(right) && !mem_is_metatype(right))
2081+
return mem_add_dt(right, left, result);
2082+
if (!mem_is_interval(right) || mem_is_metatype(right)) {
2083+
diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(right),
2084+
"datetime or interval");
2085+
return -1;
2086+
}
2087+
mem_set_interval(result, &left->u.itv);
2088+
return interval_interval_add(&result->u.itv, &right->u.itv);
2089+
}
2090+
20472091
int
2048-
mem_sub(const struct Mem *left, const struct Mem *right, struct Mem *result)
2092+
mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
20492093
{
20502094
if (mem_is_any_null(left, right)) {
20512095
mem_set_null(result);
20522096
return 0;
20532097
}
2054-
if (check_types_numeric_arithmetic(left, right) != 0)
2098+
if (!mem_is_metatype(left)) {
2099+
if (mem_is_num(left))
2100+
return mem_add_num(left, right, result);
2101+
if (mem_is_datetime(left))
2102+
return mem_add_dt(left, right, result);
2103+
if (mem_is_interval(left))
2104+
return mem_add_itv(left, right, result);
2105+
}
2106+
diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(left),
2107+
"integer, decimal, double, datetime or interval");
2108+
return -1;
2109+
}
2110+
2111+
/**
2112+
* Subtract the second MEM from the first MEM and write the result to the third
2113+
* MEM. The first and the second MEMs should be of numeric types. The result is
2114+
* of numeric type.
2115+
*/
2116+
static int
2117+
mem_sub_num(const struct Mem *left, const struct Mem *right, struct Mem *result)
2118+
{
2119+
assert(mem_is_num(left) && !mem_is_metatype(left));
2120+
if (!mem_is_num(right) || mem_is_metatype(right)) {
2121+
diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(right),
2122+
"integer, decimal or double");
20552123
return -1;
2124+
}
20562125
if (((left->type | right->type) & MEM_TYPE_DOUBLE) != 0) {
20572126
double a;
20582127
double b;
@@ -2086,6 +2155,72 @@ mem_sub(const struct Mem *left, const struct Mem *right, struct Mem *result)
20862155
return 0;
20872156
}
20882157

2158+
/**
2159+
* Subtract the second MEM from the first MEM and write the result to the third
2160+
* MEM. The first MEM should be of DATETIME type and the second MEMs should be
2161+
* of INTERVAL or DATETIME type. The result is of INTERVAL type if the second
2162+
* MEM is of DATETIME type and the result if of DATETIME type if the second
2163+
* argument if of INTERVAL type.
2164+
*/
2165+
static int
2166+
mem_sub_dt(const struct Mem *left, const struct Mem *right, struct Mem *result)
2167+
{
2168+
assert(mem_is_datetime(left) && !mem_is_metatype(left));
2169+
if (mem_is_datetime(right) && !mem_is_metatype(right)) {
2170+
struct interval res;
2171+
memset(&res, 0, sizeof(res));
2172+
mem_set_interval(result, &res);
2173+
return datetime_datetime_sub(&result->u.itv, &left->u.dt,
2174+
&right->u.dt);
2175+
}
2176+
2177+
if (!mem_is_interval(right) || mem_is_metatype(right)) {
2178+
diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(right),
2179+
"datetime or interval");
2180+
return -1;
2181+
}
2182+
mem_set_datetime(result, &left->u.dt);
2183+
return datetime_increment_by(&result->u.dt, -1, &right->u.itv);
2184+
}
2185+
2186+
/**
2187+
* Subtract the second MEM from the first MEM and write the result to the third
2188+
* MEM. The first and the second MEMs should be of INTERVAL types. The result is
2189+
* of INTERVAL type.
2190+
*/
2191+
static int
2192+
mem_sub_itv(const struct Mem *left, const struct Mem *right, struct Mem *result)
2193+
{
2194+
assert(mem_is_interval(left) && !mem_is_metatype(left));
2195+
if (!mem_is_interval(right) || mem_is_metatype(right)) {
2196+
diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(right),
2197+
"interval");
2198+
return -1;
2199+
}
2200+
mem_set_interval(result, &left->u.itv);
2201+
return interval_interval_sub(&result->u.itv, &right->u.itv);
2202+
}
2203+
2204+
int
2205+
mem_sub(const struct Mem *left, const struct Mem *right, struct Mem *result)
2206+
{
2207+
if (mem_is_any_null(left, right)) {
2208+
mem_set_null(result);
2209+
return 0;
2210+
}
2211+
if (!mem_is_metatype(left)) {
2212+
if (mem_is_num(left))
2213+
return mem_sub_num(left, right, result);
2214+
if (mem_is_datetime(left))
2215+
return mem_sub_dt(left, right, result);
2216+
if (mem_is_interval(left))
2217+
return mem_sub_itv(left, right, result);
2218+
}
2219+
diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(left),
2220+
"integer, decimal, double, datetime or interval");
2221+
return -1;
2222+
}
2223+
20892224
int
20902225
mem_mul(const struct Mem *left, const struct Mem *right, struct Mem *result)
20912226
{

src/box/sql/mem.h

+6
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@ mem_is_datetime(const struct Mem *mem)
196196
return mem->type == MEM_TYPE_DATETIME;
197197
}
198198

199+
static inline bool
200+
mem_is_interval(const struct Mem *mem)
201+
{
202+
return mem->type == MEM_TYPE_INTERVAL;
203+
}
204+
199205
static inline bool
200206
mem_is_bytes(const struct Mem *mem)
201207
{

test/sql-luatest/datetime_test.lua

+109-6
Original file line numberDiff line numberDiff line change
@@ -1321,9 +1321,7 @@ g.test_datetime_23_2 = function()
13211321
g.server:exec(function()
13221322
local t = require('luatest')
13231323
local sql = [[SELECT dt + 1 FROM t2;]]
1324-
local res = [[Type mismatch: can not convert ]]..
1325-
[[datetime(2001-01-01T01:00:00Z) ]]..
1326-
[[to integer, decimal or double]]
1324+
local res = [[Type mismatch: can not convert integer(1) to interval]]
13271325
local _, err = box.execute(sql)
13281326
t.assert_equals(err.message, res)
13291327
end)
@@ -1333,9 +1331,8 @@ g.test_datetime_23_3 = function()
13331331
g.server:exec(function()
13341332
local t = require('luatest')
13351333
local sql = [[SELECT dt - 1 FROM t2;]]
1336-
local res = [[Type mismatch: can not convert ]]..
1337-
[[datetime(2001-01-01T01:00:00Z) ]]..
1338-
[[to integer, decimal or double]]
1334+
local res = [[Type mismatch: can not convert integer(1) to ]]..
1335+
[[datetime or interval]]
13391336
local _, err = box.execute(sql)
13401337
t.assert_equals(err.message, res)
13411338
end)
@@ -2259,3 +2256,109 @@ g.test_datetime_30_18 = function()
22592256
t.assert_equals(rows, res)
22602257
end)
22612258
end
2259+
2260+
-- Make sure that arithmetic for datetime works as intended.
2261+
g.test_datetime_31_1 = function()
2262+
g.server:exec(function()
2263+
local t = require('luatest')
2264+
local dt = require('datetime')
2265+
local itv = dt.interval
2266+
local dt1 = dt.new({year = 2001, month = 1, day = 1, hour = 1})
2267+
local dt2 = dt.new({year = 2002, month = 2, day = 2, hour = 2})
2268+
local itv1 = itv.new({year = 1, month = 1, day = 1, hour = 1})
2269+
t.assert_equals(dt2 - dt1, itv1)
2270+
2271+
local rows = box.execute([[SELECT $1 - $2;]], {dt2, dt1}).rows
2272+
t.assert_equals(rows, {{itv1}})
2273+
end)
2274+
end
2275+
2276+
g.test_datetime_31_2 = function()
2277+
g.server:exec(function()
2278+
local t = require('luatest')
2279+
local dt = require('datetime')
2280+
local dt1 = dt.new({year = 2001, month = 1, day = 1, hour = 1})
2281+
local dt2 = dt.new({year = 2002, month = 2, day = 2, hour = 2})
2282+
local _, err = box.execute([[SELECT $1 + $2;]], {dt1, dt2})
2283+
t.assert_equals(err.message, [[Type mismatch: can not convert ]]..
2284+
[[datetime(2002-02-02T02:00:00Z) to ]]..
2285+
[[interval]])
2286+
end)
2287+
end
2288+
2289+
g.test_datetime_31_3 = function()
2290+
g.server:exec(function()
2291+
local t = require('luatest')
2292+
local dt = require('datetime')
2293+
local itv = dt.interval
2294+
local dt1 = dt.new({year = 2001, month = 1, day = 1, hour = 1})
2295+
local dt2 = dt.new({year = 2002, month = 2, day = 2, hour = 2})
2296+
local itv1 = itv.new({year = 1, month = 1, day = 1, hour = 1})
2297+
local rows = box.execute([[SELECT $1 + $2;]], {dt1, itv1}).rows
2298+
t.assert_equals(rows, {{dt2}})
2299+
end)
2300+
end
2301+
2302+
g.test_datetime_31_4 = function()
2303+
g.server:exec(function()
2304+
local t = require('luatest')
2305+
local dt = require('datetime')
2306+
local itv = dt.interval
2307+
local dt1 = dt.new({year = 2001, month = 1, day = 1, hour = 1})
2308+
local dt2 = dt.new({year = 2002, month = 2, day = 2, hour = 2})
2309+
local itv1 = itv.new({year = 1, month = 1, day = 1, hour = 1})
2310+
local rows = box.execute([[SELECT $1 - $2;]], {dt2, itv1}).rows
2311+
t.assert_equals(rows, {{dt1}})
2312+
end)
2313+
end
2314+
2315+
g.test_datetime_31_5 = function()
2316+
g.server:exec(function()
2317+
local t = require('luatest')
2318+
local dt = require('datetime')
2319+
local itv = dt.interval
2320+
local dt1 = dt.new({year = 2001, month = 1, day = 1, hour = 1})
2321+
local dt2 = dt.new({year = 2002, month = 2, day = 2, hour = 2})
2322+
local itv1 = itv.new({year = 1, month = 1, day = 1, hour = 1})
2323+
local rows = box.execute([[SELECT $1 + $2;]], {itv1, dt1}).rows
2324+
t.assert_equals(rows, {{dt2}})
2325+
end)
2326+
end
2327+
2328+
g.test_datetime_31_6 = function()
2329+
g.server:exec(function()
2330+
local t = require('luatest')
2331+
local dt = require('datetime')
2332+
local itv = dt.interval
2333+
local dt1 = dt.new({year = 2001, month = 1, day = 1, hour = 1})
2334+
local itv1 = itv.new({year = 1, month = 1, day = 1, hour = 1})
2335+
local _, err = box.execute([[SELECT $1 - $2;]], {itv1, dt1})
2336+
t.assert_equals(err.message, [[Type mismatch: can not convert ]]..
2337+
[[datetime(2001-01-01T01:00:00Z) to ]]..
2338+
[[interval]])
2339+
end)
2340+
end
2341+
2342+
g.test_datetime_31_7 = function()
2343+
g.server:exec(function()
2344+
local t = require('luatest')
2345+
local itv = require('datetime').interval
2346+
local itv1 = itv.new({year = 1, month = 1, day = 1, hour = 1})
2347+
local itv2 = itv.new({year = 2, month = 3, day = 4, hour = 5})
2348+
local itv3 = itv.new({year = 3, month = 4, day = 5, hour = 6})
2349+
local rows = box.execute([[SELECT $1 - $2;]], {itv3, itv1}).rows
2350+
t.assert_equals(rows, {{itv2}})
2351+
end)
2352+
end
2353+
2354+
g.test_datetime_31_8 = function()
2355+
g.server:exec(function()
2356+
local t = require('luatest')
2357+
local itv = require('datetime').interval
2358+
local itv1 = itv.new({year = 1, month = 1, day = 1, hour = 1})
2359+
local itv2 = itv.new({year = 2, month = 3, day = 4, hour = 5})
2360+
local itv3 = itv.new({year = 3, month = 4, day = 5, hour = 6})
2361+
local rows = box.execute([[SELECT $1 + $2;]], {itv2, itv1}).rows
2362+
t.assert_equals(rows, {{itv3}})
2363+
end)
2364+
end

test/sql-luatest/gh_6773_arithmetic_operands_test.lua

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ g.test_add = function()
1515
g.server:exec(function()
1616
local t = require('luatest')
1717
local res = [[Type mismatch: can not convert string('1') to ]]..
18-
[[integer, decimal or double]]
18+
[[integer, decimal, double, datetime or interval]]
1919
local _, err = box.execute([[SELECT '1' + '2';]])
2020
t.assert_equals(err.message, res)
2121
_, err = box.execute([[SELECT 1 + '2';]])
@@ -29,7 +29,7 @@ g.test_sub = function()
2929
g.server:exec(function()
3030
local t = require('luatest')
3131
local res = [[Type mismatch: can not convert string('1') to ]]..
32-
[[integer, decimal or double]]
32+
[[integer, decimal, double, datetime or interval]]
3333
local _, err = box.execute([[SELECT '1' - '2';]])
3434
t.assert_equals(err.message, res)
3535
_, err = box.execute([[SELECT 1 - '2';]])

test/sql-luatest/interval_test.lua

+3-6
Original file line numberDiff line numberDiff line change
@@ -1242,9 +1242,8 @@ g.test_interval_23_2 = function()
12421242
g.server:exec(function()
12431243
local t = require('luatest')
12441244
local sql = [[SELECT itv + 1 FROM t0;]]
1245-
local res = [[Type mismatch: can not convert ]]..
1246-
[[interval(+1 years, 2 months, 3 days, 4 hours) to ]]..
1247-
[[integer, decimal or double]]
1245+
local res = [[Type mismatch: can not convert integer(1) to datetime ]]..
1246+
[[or interval]]
12481247
local _, err = box.execute(sql)
12491248
t.assert_equals(err.message, res)
12501249
end)
@@ -1254,9 +1253,7 @@ g.test_interval_23_3 = function()
12541253
g.server:exec(function()
12551254
local t = require('luatest')
12561255
local sql = [[SELECT itv - 1 FROM t0;]]
1257-
local res = [[Type mismatch: can not convert ]]..
1258-
[[interval(+1 years, 2 months, 3 days, 4 hours) to ]]..
1259-
[[integer, decimal or double]]
1256+
local res = [[Type mismatch: can not convert integer(1) to interval]]
12601257
local _, err = box.execute(sql)
12611258
t.assert_equals(err.message, res)
12621259
end)

test/sql-tap/array.test.lua

+2-2
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ test:do_catchsql_test(
473473
SELECT a3(1, 2, 3) + 1;
474474
]], {
475475
1, "Type mismatch: can not convert array([1, 2, 3]) to integer, "..
476-
"decimal or double"
476+
"decimal, double, datetime or interval"
477477
})
478478

479479
test:do_catchsql_test(
@@ -482,7 +482,7 @@ test:do_catchsql_test(
482482
SELECT a3(1, 2, 3) - 1;
483483
]], {
484484
1, "Type mismatch: can not convert array([1, 2, 3]) to integer, "..
485-
"decimal or double"
485+
"decimal, double, datetime or interval"
486486
})
487487

488488
test:do_catchsql_test(

0 commit comments

Comments
 (0)