Bug #95274 Unexpected result in PERIOD_ADD

Submitted: 7 May 2019 9:20 Modified: 31 Jan 2020 13:29
Reporter: Song Guo Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: DML Severity:S3 (Non-critical)
Version:8.0.15, 5.7.26, 5.6.44 OS:Any
Assigned to: CPU Architecture:Any

[7 May 2019 9:20] Song Guo
Description:
In function

bool valid_period(ulong period) {
  if (period <= 0) return false;
  if ((period % 100) == 0) return false;
  if ((period % 100) > 12) return false;
  return true;
}

and function

longlong Item_func_period_add::val_int() {
  DBUG_ASSERT(fixed == 1);
  ulong period = (ulong)args[0]->val_int();
  int months = (int)args[1]->val_int();

  if ((null_value = args[0]->null_value || args[1]->null_value))
    return 0; /* purecov: inspected */
  if (!valid_period(period)) {
    my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
    return error_int();
  }
  return (longlong)convert_month_to_period(convert_period_to_month(period) +
                                           months);
}

As period is unsigned long, it will never less than 0. And will produce wrong result when period is less than 0 in PERIOD_ADD and PERIOD_DIFF

How to repeat:
mysql> SELECT PERIOD_ADD(-100912, 10);select version();
+-------------------------+
| PERIOD_ADD(-100912, 10) |
+-------------------------+
|                 -100814 |
+-------------------------+
1 row in set (0.00 sec)

+-----------+
| version() |
+-----------+
| 8.0.15    |
+-----------+
1 row in set (0.00 sec)

mysql> SELECT PERIOD_ADD(-100915, 10);select version();
+-------------------------+
| PERIOD_ADD(-100915, 10) |
+-------------------------+
|                 -100905 |
+-------------------------+
1 row in set (0.00 sec)                                
                           
+-----------+              
| version() |              
+-----------+              
| 8.0.15    |              
+-----------+          
1 row in set (0.00 sec)
             
mysql> SELECT PERIOD_ADD(-100916, 10);select version();
ERROR 1210 (HY000): Incorrect arguments to period_add

Suggested fix:
bool valid_period(longlong period) {
  if (period <= 0) return false;
  if ((period % 100) == 0) return false;
  if ((period % 100) > 12) return false;
  return true;
}

and function

longlong Item_func_period_add::val_int() {
  DBUG_ASSERT(fixed == 1);
  longlong period = args[0]->val_int();
  int months = (int)args[1]->val_int();

  if ((null_value = args[0]->null_value || args[1]->null_value))
    return 0; /* purecov: inspected */
  if (!valid_period(period)) {
    my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
    return error_int();
  }
  return (longlong)convert_month_to_period(convert_period_to_month(period) +
                                           months);
}

longlong Item_func_period_diff::val_int() {
  DBUG_ASSERT(fixed == 1);
  longlong period1 = args[0]->val_int();
  longlong period2 = args[1]->val_int();

Also should take care of integer overflow.
[7 May 2019 10:42] MySQL Verification Team
Hello Song Guo,

Thank you for the report.
Imho this is properly handled in 8.0.16 but issue still exists in 5.6/5.7.

###
-
 bin/mysql -uroot -S /tmp/mysql_ushastry.sock
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.16 MySQL Community Server - GPL

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show variables like 'sql_mode';
+---------------+-----------------------------------------------------------------------------------------------------------------------+
| Variable_name | Value                                                                                                                 |
+---------------+-----------------------------------------------------------------------------------------------------------------------+
| sql_mode      | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+---------------+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT PERIOD_ADD(-100915, 10);
ERROR 1210 (HY000): Incorrect arguments to period_add
mysql> SELECT PERIOD_ADD(-100912, 10);
ERROR 1210 (HY000): Incorrect arguments to period_add
mysql>
mysql> SELECT PERIOD_ADD(-100916, 10);
ERROR 1210 (HY000): Incorrect arguments to period_add
mysql>

- 5.7.26/5.6.44 - behaves wrongly

 bin/mysql -uroot -S /tmp/mysql_ushastry.sock                          
 Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.26 MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show variables like 'sql_mode';
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------+
| Variable_name | Value                                                                                                                                     |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------+
| sql_mode      | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT PERIOD_ADD(-100915, 10);
+-------------------------+
| PERIOD_ADD(-100915, 10) |
+-------------------------+
|             18611424103 |
+-------------------------+
1 row in set (0.00 sec)

mysql> SELECT PERIOD_ADD(-100912, 10);
+-------------------------+
| PERIOD_ADD(-100912, 10) |
+-------------------------+
|             18611424106 |
+-------------------------+
1 row in set (0.00 sec)

mysql> SELECT PERIOD_ADD(-100916, 10);
+-------------------------+
| PERIOD_ADD(-100916, 10) |
+-------------------------+
|             18611424102 |
+-------------------------+
1 row in set (0.00 sec)

regards,
Umesh
[6 Jun 2019 12:29] Tor Didriksen
Posted by developer:
 
Fixed by patch for Bug#29175262
[31 Jan 2020 13:29] Erlend Dahl
Fixed in 8.0.16 under the heading of

Bug#29175262 ITEM_FUNC_PERIOD_ADD::VAL_INT BEHAVES DIFFERENTLY ON WINDOWS: GIVES ERROR

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.040 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.