commit - 6e77907baa3cbeebc79241cc0046a539a09e3f2c
commit + f57be571b5e4cc8d57c7e97c15b52df37ad6f12c
blob - /dev/null
blob + ad1d46553852af85f5a0bf5a2fcd5d370a33a26a (mode 644)
--- /dev/null
+++ changelogs/unreleased/gh-10391-forbid-non-integers-sql-datetime.md
+## bugfix/datetime
+
+- Forbid non-integers in SQL's `CAST({}) AS datetime` (gh-10391).
blob - a66ed101cab29fbd0ecfa8108187eef628fbfb90
blob + 08a6ee5ed45a601d2995e7fb537d6fbcd060ee6a
--- src/lib/core/datetime.c
+++ src/lib/core/datetime.c
return (int64_t)date->epoch + date->tzoffset * 60;
}
+static int
+is_integer(double num)
+{
+ return roundf(num) == num;
+}
+
/**
* Resolve tzindex encoded timezone from @sa date using Olson facilities.
* @param[in] epoch decode input epoch time (in seconds).
static int
datetime_from_fields(struct datetime *dt, const struct dt_fields *fields)
{
+ if (!is_integer(fields->year) ||
+ !is_integer(fields->month) ||
+ !is_integer(fields->day) ||
+ !is_integer(fields->hour) ||
+ !is_integer(fields->min) ||
+ !is_integer(fields->sec) ||
+ !is_integer(fields->msec) ||
+ !is_integer(fields->usec) ||
+ !is_integer(fields->nsec) ||
+ !is_integer(fields->tzoffset))
+ return -1;
+
if (fields->count_usec > 1)
return -1;
double nsec = fields->msec * 1000000 + fields->usec * 1000 +
blob - 837b9520b8a7b46e9fdf0172f8a8e37ef32b5e7e
blob + 3781d507a08c0da4453e4a5384940f195dc9f3e7
--- test/sql-luatest/datetime_test.lua
+++ test/sql-luatest/datetime_test.lua
local dt = require('datetime')
local dt1 = dt.new({year = 1})
local sql = [[SELECT CAST({'year': 1.1} AS DATETIME);]]
- t.assert_equals(box.execute(sql).rows, {{dt1}})
+ local _, err, res
+ _, err = box.execute(sql)
+ res = [[Type mismatch: can not convert ]]..
+ [[map({"year": 1.1}) to datetime]]
+ t.assert_equals(err.message, res)
sql = [[SELECT CAST({'year': 1.1e0} AS DATETIME);]]
- t.assert_equals(box.execute(sql).rows, {{dt1}})
+ _, err = box.execute(sql)
+ res = [[Type mismatch: can not convert ]]..
+ [[map({"year": 1.1}) to datetime]]
+ t.assert_equals(err.message, res)
+ -- The timestamp value should be integer if the fractional
+ -- part for the last second is set via the nsec.
+ sql = [[SELECT CAST({'timestamp': 1.1, 'nsec': 1} AS DATETIME);]]
+ _, err = box.execute(sql)
+ res = [[Type mismatch: can not convert ]]..
+ [[map({"timestamp": 1.1, "nsec": 1}) to datetime]]
+ t.assert_equals(err.message, res)
+
+ -- The timestamp value should be integer if the fractional
+ -- part for the last second is set via the usec.
+ sql = [[SELECT CAST({'timestamp': 1.1, 'usec': 1} AS DATETIME);]]
+ _, err = box.execute(sql)
+ res = [[Type mismatch: can not convert ]]..
+ [[map({"timestamp": 1.1, "usec": 1}) to datetime]]
+ t.assert_equals(err.message, res)
+
+ -- The timestamp value should be integer if the fractional
+ -- part for the last second is set via the msec.
+ sql = [[SELECT CAST({'timestamp': 1.1, 'msec': 1} AS DATETIME);]]
+ _, err = box.execute(sql)
+ res = [[Type mismatch: can not convert ]]..
+ [[map({"timestamp": 1.1, "msec": 1}) to datetime]]
+ t.assert_equals(err.message, res)
+
sql = [[SELECT CAST({'year': 1} AS DATETIME);]]
t.assert_equals(box.execute(sql).rows, {{dt1}})
end)