diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c index 5e6d0e1b335c05589ff03dacea5255aae439b46c..96159a11d50c055a1702ff09a2ba87b1be9a14cd 100644 --- a/src/box/sql/mem.c +++ b/src/box/sql/mem.c @@ -1308,6 +1308,18 @@ map_to_datetime(struct Mem *mem) return 0; } +/** Convert MEM from MAP to INTERVAL. */ +static inline int +map_to_interval(struct Mem *mem) +{ + assert(mem->type == MEM_TYPE_MAP); + struct interval itv; + if (interval_from_map(&itv, mem->z) != 0) + return -1; + mem_set_interval(mem, &itv); + return 0; +} + int mem_to_int(struct Mem *mem) { @@ -1490,6 +1502,8 @@ mem_cast_explicit(struct Mem *mem, enum field_type type) mem->flags = 0; return 0; case FIELD_TYPE_INTERVAL: + if (mem->type == MEM_TYPE_MAP) + return map_to_interval(mem); if (mem->type != MEM_TYPE_INTERVAL) return -1; mem->flags = 0; diff --git a/src/lib/core/datetime.c b/src/lib/core/datetime.c index 7cfbd95e9d371a02c44ba2557c0469fb5f40ae9d..07f9988a1f219759f66fd9b734a7f0ecbfcf4767 100644 --- a/src/lib/core/datetime.c +++ b/src/lib/core/datetime.c @@ -929,6 +929,58 @@ get_double_from_mp(const char **data, double *value) return 0; } +/** Parse msgpack value and convert it to int32, if possible. */ +static int +get_int32_from_mp(const char **data, int32_t *value) +{ + switch (mp_typeof(**data)) { + case MP_INT: { + int64_t val = mp_decode_int(data); + if (val < INT32_MIN) + return -1; + *value = val; + break; + } + case MP_UINT: { + uint64_t val = mp_decode_uint(data); + if (val > INT32_MAX) + return -1; + *value = val; + break; + } + case MP_DOUBLE: { + double val = mp_decode_double(data); + if (val > (double)INT32_MAX || val < (double)INT32_MIN) + return -1; + if (val != floor(val)) + return -1; + *value = val; + break; + } + case MP_EXT: { + int8_t type; + uint32_t len = mp_decode_extl(data, &type); + if (type != MP_DECIMAL) + return -1; + decimal_t dec; + if (decimal_unpack(data, len, &dec) == NULL) + return -1; + if (!decimal_is_int(&dec)) + return -1; + int64_t val; + if (decimal_to_int64(&dec, &val) == NULL) + return -1; + if (val < INT32_MIN || val > INT32_MAX) + return -1; + *value = val; + break; + } + default: + return -1; + } + return 0; +} + /** Define field of DATETIME value from field of given MAP value.*/ static int map_field_to_dt_field(struct dt_fields *fields, const char **data) @@ -1050,3 +1102,77 @@ datetime_from_map(struct datetime *dt, const char *data) } return datetime_from_fields(dt, &fields); } + +/** Define field of INTERVAL value from field of given MAP value.*/ +static int +map_field_to_itv_field(struct interval *itv, const char **data) +{ + if (mp_typeof(**data) != MP_STR) { + mp_next(data); + mp_next(data); + return 0; + } + uint32_t size; + const char *str = mp_decode_str(data, &size); + double *dvalue = NULL; + int32_t *ivalue = NULL; + if (strncmp(str, "year", size) == 0) { + ivalue = &itv->year; + } else if (strncmp(str, "month", size) == 0) { + ivalue = &itv->month; + } else if (strncmp(str, "week", size) == 0) { + ivalue = &itv->week; + } else if (strncmp(str, "day", size) == 0) { + dvalue = &itv->day; + } else if (strncmp(str, "hour", size) == 0) { + dvalue = &itv->hour; + } else if (strncmp(str, "min", size) == 0) { + dvalue = &itv->min; + } else if (strncmp(str, "sec", size) == 0) { + dvalue = &itv->sec; + } else if (strncmp(str, "nsec", size) == 0) { + ivalue = &itv->nsec; + } else if (strncmp(str, "adjust", size) == 0) { + if (mp_typeof(**data) != MP_STR) + return -1; + uint32_t vsize; + const char *val = mp_decode_str(data, &vsize); + if (strncasecmp(val, "none", vsize) == 0) + itv->adjust = DT_LIMIT; + else if (strncasecmp(val, "last", vsize) == 0) + itv->adjust = DT_SNAP; + else if (strncasecmp(val, "excess", vsize) == 0) + itv->adjust = DT_EXCESS; + else + return -1; + return 0; + } else { + mp_next(data); + return 0; + } + if (dvalue != NULL) { + double val; + if (get_double_from_mp(data, &val) != 0) + return -1; + if (val != floor(val)) + return -1; + *dvalue = val; + return 0; + } + assert(ivalue != NULL); + return get_int32_from_mp(data, ivalue); +} + +int +interval_from_map(struct interval *itv, const char *data) +{ + assert(mp_typeof(*data) == MP_MAP); + uint32_t len = mp_decode_map(&data); + memset(itv, 0, sizeof(*itv)); + itv->adjust = DT_LIMIT; + for (uint32_t i = 0; i < len; ++i) { + if (map_field_to_itv_field(itv, &data) != 0) + return -1; + } + return interval_check_args(itv) == 0 ? 0 : -1; +} diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h index 03288c2e7b3c02823e55235f8a4fdfcd0fbe7efe..9b7a8a087d010c879f1fbf28b090a20643e12064 100644 --- a/src/lib/core/datetime.h +++ b/src/lib/core/datetime.h @@ -385,6 +385,10 @@ datetime_usec(const struct datetime *date) int datetime_from_map(struct datetime *dt, const char *data); +/** Parse MAP value and construct INTERVAL value. */ +int +interval_from_map(struct interval *itv, const char *data); + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/test/sql-luatest/datetime_test.lua b/test/sql-luatest/datetime_test.lua index 51bfbbd1ac3c9176e67c1ae543101c744975697a..ad04e4adecf93fd0438bc49d31e32cef4acd182d 100644 --- a/test/sql-luatest/datetime_test.lua +++ b/test/sql-luatest/datetime_test.lua @@ -2680,3 +2680,183 @@ g.test_datetime_33_3 = function() t.assert_equals(box.execute(sql).rows, {{dt1}}) end) end + +-- Make sure cast from MAP to INTERVAL works as intended. + +-- +-- The result of CAST() from MAP value to INTERVAL must be equal to the result +-- of calling require('datetime').interval.new() with the corresponding table as +-- an argument. +-- +g.test_datetime_34_1 = function() + g.server:exec(function() + local t = require('luatest') + local itv = require('datetime').interval + local v = setmetatable({}, { __serialize = 'map' }) + local sql = [[SELECT CAST(#v AS INTERVAL);]] + t.assert_equals(box.execute(sql, {{['#v'] = v}}).rows, {{itv.new(v)}}) + + v.something = 1 + t.assert_equals(box.execute(sql, {{['#v'] = v}}).rows, {{itv.new(v)}}) + + v = {year = 1, month = 1, week = 1, day = 1, hour = 1, min = 1, sec = 1, + nsec = 1, adjust = 'none'} + t.assert_equals(box.execute(sql, {{['#v'] = v}}).rows, {{itv.new(v)}}) + end) +end + +-- +-- Make sure an error is thrown if the INTERVAL value cannot be constructed from +-- the corresponding table. +-- +g.test_datetime_34_2 = function() + g.server:exec(function() + local t = require('luatest') + local sql = [[SELECT CAST(#v AS INTERVAL);]] + + -- "year" cannot be more than 11759221. + local v = {year = 11759222} + local _, err = box.execute(sql, {{['#v'] = v}}) + local res = [[Type mismatch: can not convert ]].. + [[map({"year": 11759222}) to interval]] + t.assert_equals(err.message, res) + + -- "year" cannot be less than -11759221. + v = {year = -11759222} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"year": -11759222}) to interval]] + t.assert_equals(err.message, res) + + -- "month" cannot be more than 141110652. + v = {month = 141110653} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"month": 141110653}) to interval]] + t.assert_equals(err.message, res) + + -- "month" cannot be less than -141110652. + v = {month = -141110653} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"month": -141110653}) to interval]] + t.assert_equals(err.message, res) + + -- "week" cannot be more than 613579352. + v = {week = 613579353} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"week": 613579353}) to interval]] + t.assert_equals(err.message, res) + + -- "week" cannot be less than -613579352. + v = {week = -613579353} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"week": -613579353}) to interval]] + t.assert_equals(err.message, res) + + -- "day" cannot be more than 4295055470. + v = {day = 4295055471} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"day": 4295055471}) to interval]] + t.assert_equals(err.message, res) + + -- "day" cannot be less than -4295055470. + v = {day = -4295055471} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"day": -4295055471}) to interval]] + t.assert_equals(err.message, res) + + -- "hour" cannot be more than 103081331286. + v = {hour = 103081331287} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"hour": 103081331287}) to interval]] + t.assert_equals(err.message, res) + + -- "hour" cannot be less than 103081331286. + v = {hour = -103081331287} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"hour": -103081331287}) to interval]] + t.assert_equals(err.message, res) + + -- "min" cannot be more than 6184879877160. + v = {min = 6184879877161} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"min": 6184879877161}) to interval]] + t.assert_equals(err.message, res) + + -- "min" cannot be less than -6184879877160. + v = {min = -6184879877161} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"min": -6184879877161}) to interval]] + t.assert_equals(err.message, res) + + -- "sec" cannot be more than 371092792629600. + v = {sec = 371092792629601} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"sec": 371092792629601}) to interval]] + t.assert_equals(err.message, res) + + -- "sec" cannot be less than -371092792629600. + v = {sec = -371092792629601} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert ]].. + [[map({"sec": -371092792629601}) to interval]] + t.assert_equals(err.message, res) + + -- "nsec" cannot be more than 2147483647. + v = {nsec = 2147483648} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert map({"nsec": 2147483648}) ]].. + [[to interval]] + t.assert_equals(err.message, res) + + -- "nsec" cannot be less than -2147483647. + v = {nsec = -2147483648} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert map({"nsec": -2147483648}) ]].. + [[to interval]] + t.assert_equals(err.message, res) + + -- "adjust" cannot be anything other than "none", "excess", "last". + v = {adjust = 1} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert map({"adjust": 1}) ]].. + [[to interval]] + t.assert_equals(err.message, res) + + v = {adjust = 'asd'} + _, err = box.execute(sql, {{['#v'] = v}}) + res = [[Type mismatch: can not convert map({"adjust": "asd"}) ]].. + [[to interval]] + t.assert_equals(err.message, res) + end) +end + +-- +-- Make sure that any of the DECIMAL, INTEGER, and DOUBLE values can be used as +-- values in the MAP converted to a INTERVAL. +-- +g.test_datetime_34_3 = function() + g.server:exec(function() + local t = require('luatest') + local itv = require('datetime').interval + local dt1 = itv.new({year = 1}) + local sql = [[SELECT CAST({'year': 1.0} AS INTERVAL);]] + t.assert_equals(box.execute(sql).rows, {{dt1}}) + + sql = [[SELECT CAST({'year': 1.e0} AS INTERVAL);]] + t.assert_equals(box.execute(sql).rows, {{dt1}}) + + sql = [[SELECT CAST({'year': 1} AS INTERVAL);]] + t.assert_equals(box.execute(sql).rows, {{dt1}}) + end) +end