Commit e71aecfd authored by Alexander Barkov's avatar Alexander Barkov

MDEV-33299 Assertion `(tm->tv_usec % (int) log_10_int[6 - dec]) == 0' failed...

MDEV-33299 Assertion `(tm->tv_usec % (int) log_10_int[6 - dec]) == 0' failed in void my_timestamp_to_binary(const timeval*, uchar*, uint)

This original query:

(1)  SELECT ts0 FROM t1
     WHERE DATE(ts0) <= '2024-01-23';

was rewritten (by MDEV-8320) to:

(2)  SELECT ts0 FROM t1
     WHERE ts0 <= '2024-01-23 23:59.59.999999';
     -- DATETIME comparison, Item_datetime on the right side

which was further optimized (by MDEV-32148) to:

(3)  SELECT ts0 FROM t1
     WHERE ts0 <= TIMESTAMP/* WITH LOCAL TIME ZONE*/ '2024-01-23 23:59.59.999999';
     -- TIMESTAMP comparison, Item_timestamp_literal on the right side

The origin of the problem was in (2) - in the MDEV-8320 related code.
The recent new code for MDEV-32148 revealed this problem.

Item_datetime on step (2) was always created in an inconsistent way:
- with Item::decimals==0
- with ltime.second_part==999999,
  without taking into account the precision of the left side
  (e.g. ts0 in the above example)

On step (3), Item_timestamp_literal was created in an inconsistent way too,
because it copied the inconsistent data from step (2):
- with Item::decimals==0       (copied from Item_datetime::decimals)
- with m_value.tv_usec==999999 (copied from ltime.second_part of Item_datetime)

Later, the Item_timestamp_literal performed save_in_field()
and crashed in my_timestamp_to_binary() on a DBUG_ASSERT checking
consistency between the fractional precision and the fractional seconds value.

Fix:

On step (2) create Item_datetime with truncating maximum possible
second_part value of 999999 according to the the left side fractional
second precision. So for example it sets second_part as follows:
- 000000 for TIMESTAMP(0)
- 999000 for TIMESTAMP(3)
- 999999 for TIMESTAMP(6)

This automatically makes the code create a consistent Item_timestamp_literal
on step (3).

This also makes TIMESTAMP comparison work faster, because now
Item_timestamp_literal is created with Item::decimals value equal
to the Item_field (which is on the other side of the comparison),
so the low level function Type_handler_timestamp_common::cmp_native()
goes the fastest execution path optimized for the case when both sides
have equal fractional precision.

Adding a helper class TimeOfDay to reuse the code when populating:
- the last datetime point for YEAR()
- the last datetime point for DATE()
with a given fractional precision.

This class also helped to unify the equal code in create_start_bound()
and create_end_bound() into a single method create_bound().
parent 83a79ba3
...@@ -2026,3 +2026,186 @@ DROP TABLE t2; ...@@ -2026,3 +2026,186 @@ DROP TABLE t2;
DROP TABLE t1; DROP TABLE t1;
SET note_verbosity=DEFAULT; SET note_verbosity=DEFAULT;
# End of 10.6 tests # End of 10.6 tests
#
# Start of 11.3 tests
#
#
# MDEV-33299 Assertion `(tm->tv_usec % (int) log_10_int[6 - dec]) == 0' failed in void my_timestamp_to_binary(const timeval*, uchar*, uint)
#
CREATE TABLE t1(a TIMESTAMP,KEY(a));
SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23' ;
a
SELECT * FROM t1 WHERE YEAR(a) <= 2024;
a
DROP TABLE t1;
CREATE TABLE t1(a TIMESTAMP,KEY(a));
INSERT INTO t1 VALUES ('2023-12-31 23:59:59');
INSERT INTO t1 VALUES ('2024-01-22 10:20:30');
INSERT INTO t1 VALUES ('2024-01-23 10:20:30');
INSERT INTO t1 VALUES ('2024-01-23 23:59:59');
INSERT INTO t1 VALUES ('2024-01-24 00:00:00');
INSERT INTO t1 VALUES ('2024-12-31 23:59:59');
INSERT INTO t1 VALUES ('2025-01-01 00:00:00');
SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
a
2023-12-31 23:59:59
2024-01-22 10:20:30
2024-01-23 10:20:30
2024-01-23 23:59:59
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 5 NULL 4 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` <= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-01-23 23:59:59'
SELECT * FROM t1 WHERE YEAR(a) <= 2024;
a
2023-12-31 23:59:59
2024-01-22 10:20:30
2024-01-23 10:20:30
2024-01-23 23:59:59
2024-01-24 00:00:00
2024-12-31 23:59:59
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) <= 2024;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 5 NULL 6 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` <= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-12-31 23:59:59'
SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
a
2024-01-24 00:00:00
2024-12-31 23:59:59
2025-01-01 00:00:00
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 5 NULL 3 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` >= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-01-24 00:00:00'
SELECT * FROM t1 WHERE YEAR(a) >= 2024;
a
2024-01-22 10:20:30
2024-01-23 10:20:30
2024-01-23 23:59:59
2024-01-24 00:00:00
2024-12-31 23:59:59
2025-01-01 00:00:00
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) >= 2024;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 5 NULL 6 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` >= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-01-01 00:00:00'
DROP TABLE t1;
CREATE TABLE t1(a TIMESTAMP(3),KEY(a));
INSERT INTO t1 VALUES ('2023-12-31 23:59:59.999');
INSERT INTO t1 VALUES ('2024-01-22 10:20:30.001');
INSERT INTO t1 VALUES ('2024-01-23 10:20:30.002');
INSERT INTO t1 VALUES ('2024-01-23 23:59:59.999');
INSERT INTO t1 VALUES ('2024-01-24 00:00:00.000');
INSERT INTO t1 VALUES ('2024-12-31 23:59:59.999');
INSERT INTO t1 VALUES ('2025-01-01 00:00:00.000');
SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
a
2023-12-31 23:59:59.999
2024-01-22 10:20:30.001
2024-01-23 10:20:30.002
2024-01-23 23:59:59.999
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 7 NULL 4 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` <= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-01-23 23:59:59.999'
SELECT * FROM t1 WHERE YEAR(a) <= 2024;
a
2023-12-31 23:59:59.999
2024-01-22 10:20:30.001
2024-01-23 10:20:30.002
2024-01-23 23:59:59.999
2024-01-24 00:00:00.000
2024-12-31 23:59:59.999
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) <= 2024;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 7 NULL 6 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` <= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-12-31 23:59:59.999'
SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
a
2024-01-24 00:00:00.000
2024-12-31 23:59:59.999
2025-01-01 00:00:00.000
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 7 NULL 3 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` >= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-01-24 00:00:00.000'
SELECT * FROM t1 WHERE YEAR(a) >= 2024;
a
2024-01-22 10:20:30.001
2024-01-23 10:20:30.002
2024-01-23 23:59:59.999
2024-01-24 00:00:00.000
2024-12-31 23:59:59.999
2025-01-01 00:00:00.000
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) >= 2024;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 7 NULL 6 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` >= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-01-01 00:00:00.000'
DROP TABLE t1;
CREATE TABLE t1(a TIMESTAMP(6),KEY(a));
INSERT INTO t1 VALUES ('2023-12-31 23:59:59.999999');
INSERT INTO t1 VALUES ('2024-01-22 10:20:30.000001');
INSERT INTO t1 VALUES ('2024-01-23 10:20:30.000002');
INSERT INTO t1 VALUES ('2024-01-23 23:59:59.999999');
INSERT INTO t1 VALUES ('2024-01-24 00:00:00.000000');
INSERT INTO t1 VALUES ('2024-12-31 23:59:59.999999');
INSERT INTO t1 VALUES ('2025-01-01 00:00:00.000000');
SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
a
2023-12-31 23:59:59.999999
2024-01-22 10:20:30.000001
2024-01-23 10:20:30.000002
2024-01-23 23:59:59.999999
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 8 NULL 4 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` <= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-01-23 23:59:59.999999'
SELECT * FROM t1 WHERE YEAR(a) <= 2024;
a
2023-12-31 23:59:59.999999
2024-01-22 10:20:30.000001
2024-01-23 10:20:30.000002
2024-01-23 23:59:59.999999
2024-01-24 00:00:00.000000
2024-12-31 23:59:59.999999
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) <= 2024;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 8 NULL 6 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` <= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-12-31 23:59:59.999999'
SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
a
2024-01-24 00:00:00.000000
2024-12-31 23:59:59.999999
2025-01-01 00:00:00.000000
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 8 NULL 3 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` >= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-01-24 00:00:00.000000'
SELECT * FROM t1 WHERE YEAR(a) >= 2024;
a
2024-01-22 10:20:30.000001
2024-01-23 10:20:30.000002
2024-01-23 23:59:59.999999
2024-01-24 00:00:00.000000
2024-12-31 23:59:59.999999
2025-01-01 00:00:00.000000
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) >= 2024;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 range a a 8 NULL 6 100.00 Using where; Using index
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` >= TIMESTAMP/*WITH LOCAL TIME ZONE*/'2024-01-01 00:00:00.000000'
DROP TABLE t1;
#
# End of 11.3 tests
#
...@@ -1183,3 +1183,78 @@ DROP TABLE t1; ...@@ -1183,3 +1183,78 @@ DROP TABLE t1;
SET note_verbosity=DEFAULT; SET note_verbosity=DEFAULT;
--echo # End of 10.6 tests --echo # End of 10.6 tests
--echo #
--echo # Start of 11.3 tests
--echo #
--echo #
--echo # MDEV-33299 Assertion `(tm->tv_usec % (int) log_10_int[6 - dec]) == 0' failed in void my_timestamp_to_binary(const timeval*, uchar*, uint)
--echo #
CREATE TABLE t1(a TIMESTAMP,KEY(a));
SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23' ;
SELECT * FROM t1 WHERE YEAR(a) <= 2024;
DROP TABLE t1;
CREATE TABLE t1(a TIMESTAMP,KEY(a));
INSERT INTO t1 VALUES ('2023-12-31 23:59:59');
INSERT INTO t1 VALUES ('2024-01-22 10:20:30');
INSERT INTO t1 VALUES ('2024-01-23 10:20:30');
INSERT INTO t1 VALUES ('2024-01-23 23:59:59');
INSERT INTO t1 VALUES ('2024-01-24 00:00:00');
INSERT INTO t1 VALUES ('2024-12-31 23:59:59');
INSERT INTO t1 VALUES ('2025-01-01 00:00:00');
SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
SELECT * FROM t1 WHERE YEAR(a) <= 2024;
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) <= 2024;
SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
SELECT * FROM t1 WHERE YEAR(a) >= 2024;
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) >= 2024;
DROP TABLE t1;
CREATE TABLE t1(a TIMESTAMP(3),KEY(a));
INSERT INTO t1 VALUES ('2023-12-31 23:59:59.999');
INSERT INTO t1 VALUES ('2024-01-22 10:20:30.001');
INSERT INTO t1 VALUES ('2024-01-23 10:20:30.002');
INSERT INTO t1 VALUES ('2024-01-23 23:59:59.999');
INSERT INTO t1 VALUES ('2024-01-24 00:00:00.000');
INSERT INTO t1 VALUES ('2024-12-31 23:59:59.999');
INSERT INTO t1 VALUES ('2025-01-01 00:00:00.000');
SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
SELECT * FROM t1 WHERE YEAR(a) <= 2024;
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) <= 2024;
SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
SELECT * FROM t1 WHERE YEAR(a) >= 2024;
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) >= 2024;
DROP TABLE t1;
CREATE TABLE t1(a TIMESTAMP(6),KEY(a));
INSERT INTO t1 VALUES ('2023-12-31 23:59:59.999999');
INSERT INTO t1 VALUES ('2024-01-22 10:20:30.000001');
INSERT INTO t1 VALUES ('2024-01-23 10:20:30.000002');
INSERT INTO t1 VALUES ('2024-01-23 23:59:59.999999');
INSERT INTO t1 VALUES ('2024-01-24 00:00:00.000000');
INSERT INTO t1 VALUES ('2024-12-31 23:59:59.999999');
INSERT INTO t1 VALUES ('2025-01-01 00:00:00.000000');
SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) <= '2024-01-23';
SELECT * FROM t1 WHERE YEAR(a) <= 2024;
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) <= 2024;
SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
EXPLAIN EXTENDED SELECT * FROM t1 WHERE DATE(a) >= '2024-01-24';
SELECT * FROM t1 WHERE YEAR(a) >= 2024;
EXPLAIN EXTENDED SELECT * FROM t1 WHERE YEAR(a) >= 2024;
DROP TABLE t1;
--echo #
--echo # End of 11.3 tests
--echo #
...@@ -4479,6 +4479,13 @@ class Item_datetime :public Item_int ...@@ -4479,6 +4479,13 @@ class Item_datetime :public Item_int
MYSQL_TIME ltime; MYSQL_TIME ltime;
public: public:
Item_datetime(THD *thd): Item_int(thd, 0) { unsigned_flag=0; } Item_datetime(THD *thd): Item_int(thd, 0) { unsigned_flag=0; }
Item_datetime(THD *thd, const Datetime &dt, decimal_digits_t dec)
:Item_int(thd, 0),
ltime(*dt.get_mysql_time())
{
unsigned_flag= 0;
decimals= dec;
}
int save_in_field(Field *field, bool no_conversions) override; int save_in_field(Field *field, bool no_conversions) override;
longlong val_int() override; longlong val_int() override;
double val_real() override { return (double)val_int(); } double val_real() override { return (double)val_int(); }
...@@ -5014,6 +5021,8 @@ class Item_timestamp_literal: public Item_literal ...@@ -5014,6 +5021,8 @@ class Item_timestamp_literal: public Item_literal
:Item_literal(thd), :Item_literal(thd),
m_value(value) m_value(value)
{ {
DBUG_ASSERT(value.is_zero_datetime() ||
!value.to_timestamp().fraction_remainder(dec));
collation= DTCollation_numeric(); collation= DTCollation_numeric();
decimals= dec; decimals= dec;
} }
......
...@@ -233,91 +233,67 @@ void Date_cmp_func_rewriter::rewrite_le_gt_lt_ge() ...@@ -233,91 +233,67 @@ void Date_cmp_func_rewriter::rewrite_le_gt_lt_ge()
} }
Item *Date_cmp_func_rewriter::create_start_bound() Item *Date_cmp_func_rewriter::create_bound(uint month, uint day,
const TimeOfDay6 &td) const
{ {
Item_datetime *res; /*
MYSQL_TIME const_arg_ts; We could always create Item_datetime with Item_datetime::decimals==6 here.
memset(&const_arg_ts, 0, sizeof(const_arg_ts)); But this would not be efficient in some cases.
const_arg_ts.time_type= MYSQL_TIMESTAMP_DATETIME;
switch (argument_func_type) {
case Item_func::YEAR_FUNC:
const_arg_ts.year= static_cast<unsigned int>(const_arg_value->val_int());
const_arg_ts.month= 1;
const_arg_ts.day= 1;
if (check_datetime_range(&const_arg_ts))
return nullptr;
res= new (thd->mem_root) Item_datetime(thd);
res->set(&const_arg_ts);
break;
case Item_func::DATE_FUNC:
if (field_ref->field->type() == MYSQL_TYPE_DATE)
return const_arg_value;
else
{
Datetime const_arg_dt(current_thd, const_arg_value);
if (!const_arg_dt.is_valid_datetime())
return nullptr;
res= new (thd->mem_root) Item_datetime(thd);
const_arg_dt.copy_to_mysql_time(&const_arg_ts);
const_arg_ts.second_part= const_arg_ts.second=
const_arg_ts.minute= const_arg_ts.hour= 0;
const_arg_ts.time_type= MYSQL_TIMESTAMP_DATETIME;
res->set(&const_arg_ts);
}
break;
default:
DBUG_ASSERT(0);
res= nullptr;
break;
}
return res;
}
Let's create an Item_datetime with Item_datetime::decimals
equal to field_ref->decimals, so if:
Item *Date_cmp_func_rewriter::create_end_bound() (1) the original statement:
{
Item_datetime *res; SELECT ts3 FROM t1 WHERE DATE(ts3) <= '2024-01-23';
MYSQL_TIME const_arg_ts;
memset(&const_arg_ts, 0, sizeof(const_arg_ts)); gets rewritten to:
const_arg_ts.time_type= MYSQL_TIMESTAMP_DATETIME;
(2) a statement with DATETIME comparison
with an Item_datetime on the right side:
SELECT ts3 FROM t1
WHERE ts3 <= '2024-01-23 23:59.59.999'; -- Item_datetime
and then gets further rewritten with help of convert_item_for_comparison()
to:
(3) a statement with TIMESTAMP comparison
with an Item_timestamp_literal on the right side
SELECT ts3 FROM t1
WHERE ts3 <= '2024-01-23 23:59:59.999'; -- Item_timestamp_literal
then we have an efficent statement calling
Type_handler_timestamp_common::cmp_native() for comparison,
which has a faster execution path when both sides have equal
fractional precision.
*/
switch (argument_func_type) { switch (argument_func_type) {
case Item_func::YEAR_FUNC: case Item_func::YEAR_FUNC:
const_arg_ts.year= static_cast<unsigned int>(const_arg_value->val_int()); {
const_arg_ts.month= 12; longlong year= const_arg_value->val_int();
const_arg_ts.day= 31; const Datetime bound(static_cast<unsigned int>(year), month, day, td);
const_arg_ts.hour= 23; if (!bound.is_valid_datetime())
const_arg_ts.minute= TIME_MAX_MINUTE; return nullptr; // "year" was out of the supported range
const_arg_ts.second= TIME_MAX_SECOND; return new (thd->mem_root) Item_datetime(thd, bound, field_ref->decimals);
const_arg_ts.second_part= TIME_MAX_SECOND_PART; }
if (check_datetime_range(&const_arg_ts))
return nullptr;
res= new (thd->mem_root) Item_datetime(thd);
res->set(&const_arg_ts);
break;
case Item_func::DATE_FUNC: case Item_func::DATE_FUNC:
if (field_ref->field->type() == MYSQL_TYPE_DATE) if (field_ref->field->type() == MYSQL_TYPE_DATE)
return const_arg_value; return const_arg_value;
else else
{ {
res= new (thd->mem_root) Item_datetime(thd); const Datetime const_arg_dt(current_thd, const_arg_value);
Datetime const_arg_dt(current_thd, const_arg_value);
if (!const_arg_dt.is_valid_datetime()) if (!const_arg_dt.is_valid_datetime())
return nullptr; return nullptr; // SQL NULL datetime
const_arg_dt.copy_to_mysql_time(&const_arg_ts); const Datetime bound(const_arg_dt.time_of_day(td));
const_arg_ts.hour= 23; return new (thd->mem_root) Item_datetime(thd, bound, field_ref->decimals);
const_arg_ts.minute= TIME_MAX_MINUTE;
const_arg_ts.second= TIME_MAX_SECOND;
const_arg_ts.second_part=TIME_MAX_SECOND_PART;
const_arg_ts.time_type= MYSQL_TIMESTAMP_DATETIME;
res->set(&const_arg_ts);
} }
break;
default: default:
DBUG_ASSERT(0); DBUG_ASSERT(0);
res= nullptr;
break; break;
} }
return res; return nullptr;
} }
......
...@@ -82,8 +82,15 @@ class Date_cmp_func_rewriter ...@@ -82,8 +82,15 @@ class Date_cmp_func_rewriter
const Type_handler *comparison_type, const Type_handler *comparison_type,
Item_func::Functype *out_func_type) const; Item_func::Functype *out_func_type) const;
void rewrite_le_gt_lt_ge(); void rewrite_le_gt_lt_ge();
Item *create_start_bound(); Item *create_bound(uint month, uint day, const TimeOfDay6 &td) const;
Item *create_end_bound(); Item *create_start_bound() const
{
return create_bound(1, 1, TimeOfDay6());
}
Item *create_end_bound() const
{
return create_bound(12, 31, TimeOfDay6::end_of_day(field_ref->decimals));
}
Item *create_cmp_func(Item_func::Functype func_type, Item *arg1, Item *arg2); Item *create_cmp_func(Item_func::Functype func_type, Item *arg1, Item *arg2);
THD *thd= nullptr; THD *thd= nullptr;
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "mysqld.h" #include "mysqld.h"
#include "lex_string.h" #include "lex_string.h"
#include "sql_type_timeofday.h"
#include "sql_array.h" #include "sql_array.h"
#include "sql_const.h" #include "sql_const.h"
#include "sql_time.h" #include "sql_time.h"
...@@ -2520,6 +2521,20 @@ class Datetime: public Temporal_with_date ...@@ -2520,6 +2521,20 @@ class Datetime: public Temporal_with_date
} }
Datetime(my_time_t unix_time, ulong second_part, Datetime(my_time_t unix_time, ulong second_part,
const Time_zone* time_zone); const Time_zone* time_zone);
Datetime(uint year_arg, uint month_arg, uint day_arg, const TimeOfDay6 &td)
{
neg= 0;
year= year_arg;
month= month_arg;
day= day_arg;
hour= td.hour();
minute= td.minute();
second= td.second();
second_part= td.usecond();
time_type= MYSQL_TIMESTAMP_DATETIME;
if (!is_valid_datetime_slow())
time_type= MYSQL_TIMESTAMP_NONE;
}
bool is_valid_datetime() const bool is_valid_datetime() const
{ {
...@@ -2665,6 +2680,12 @@ class Datetime: public Temporal_with_date ...@@ -2665,6 +2680,12 @@ class Datetime: public Temporal_with_date
return Temporal::fraction_remainder(dec); return Temporal::fraction_remainder(dec);
} }
Datetime time_of_day(const TimeOfDay6 &td) const
{
DBUG_ASSERT(is_valid_datetime()); // not SQL NULL
return Datetime(year, month, day, td);
}
Datetime &trunc(uint dec) Datetime &trunc(uint dec)
{ {
if (is_valid_datetime()) if (is_valid_datetime())
......
/* Copyright (c) 2024, MariaDB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
#ifndef SQL_TYPE_TIMEOFDAY_INCLUDED
#define SQL_TYPE_TIMEOFDAY_INCLUDED
#include "my_time.h" // TIME_MAX_MINUTE
/*
This class stores a time of the day with
fractional precision up to 6 digits.
*/
class TimeOfDay6
{
uint m_hour; // 0..23
uint m_minute; // 0..59
uint m_second; // 0..59
uint m_usecond; // 0..999999
bool is_valid_time_of_day6() const
{
return m_hour <= 23 &&
m_minute <= TIME_MAX_MINUTE &&
m_second <= TIME_MAX_SECOND &&
m_usecond <= TIME_MAX_SECOND_PART;
}
public:
TimeOfDay6()
:m_hour(0), m_minute(0), m_second(0), m_usecond(0)
{ }
// This constructor assumes the caller passes valid 'hh:mm:ss.ff' values
TimeOfDay6(uint hour, uint minute, uint second, uint usecond)
:m_hour(hour), m_minute(minute), m_second(second), m_usecond(usecond)
{
DBUG_ASSERT(is_valid_time_of_day6());
}
uint hour() const { return m_hour; }
uint minute() const { return m_minute; }
uint second() const { return m_second; }
uint usecond() const { return m_usecond; }
/*
Return the last time of the day for the given precision, e.g.:
- '23:59:59.000000' for decimals==0
- '23:59:59.999000' for decimals==3
- '23:59:59.999999' for decimals==6
*/
static TimeOfDay6 end_of_day(decimal_digits_t decimals)
{
long rem= my_time_fraction_remainder(TIME_MAX_SECOND_PART, decimals);
DBUG_ASSERT(rem >= 0 && rem <= TIME_MAX_SECOND_PART);
return TimeOfDay6(23, TIME_MAX_MINUTE, TIME_MAX_SECOND,
(uint) (TIME_MAX_SECOND_PART - (uint) rem));
}
};
#endif // SQL_TYPE_TIMEOFDAY_INCLUDED
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment