mariadb/sql/sql_window.cc

3396 lines
93 KiB
C++
Raw Normal View History

2017-06-18 06:25:01 +03:00
/*
2022-05-07 11:48:15 +02:00
Copyright (c) 2016, 2022 MariaDB
2017-06-18 06:25:01 +03:00
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-1301 USA */
#include "mariadb.h"
#include "sql_parse.h"
#include "sql_select.h"
#include "sql_list.h"
#include "item_windowfunc.h"
#include "filesort.h"
#include "sql_base.h"
#include "sql_window.h"
bool
Window_spec::check_window_names(List_iterator_fast<Window_spec> &it)
{
if (window_names_are_checked)
return false;
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
const Lex_ident_window name= this->name();
const Lex_ident_window ref_name= window_reference();
it.rewind();
Window_spec *win_spec;
while((win_spec= it++) && win_spec != this)
{
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
const Lex_ident_window win_spec_name= win_spec->name();
if (!win_spec_name.str)
break;
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
if (name.str && name.streq(win_spec_name))
{
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
my_error(ER_DUP_WINDOW_NAME, MYF(0), name.str);
return true;
}
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
if (ref_name.str && win_spec_name.streq(ref_name))
{
if (partition_list->elements)
{
my_error(ER_PARTITION_LIST_IN_REFERENCING_WINDOW_SPEC, MYF(0),
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
ref_name.str);
return true;
}
if (win_spec->order_list->elements && order_list->elements)
{
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
my_error(ER_ORDER_LIST_IN_REFERENCING_WINDOW_SPEC, MYF(0),
ref_name.str);
return true;
}
if (win_spec->window_frame)
{
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
my_error(ER_WINDOW_FRAME_IN_REFERENCED_WINDOW_SPEC, MYF(0),
ref_name.str);
return true;
}
referenced_win_spec= win_spec;
if (partition_list->elements == 0)
partition_list= win_spec->partition_list;
if (order_list->elements == 0)
order_list= win_spec->order_list;
}
}
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
if (ref_name.str && !referenced_win_spec)
{
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
my_error(ER_WRONG_WINDOW_SPEC_NAME, MYF(0), ref_name.str);
return true;
}
window_names_are_checked= true;
return false;
}
void
Window_spec::print(String *str, enum_query_type query_type)
{
str->append('(');
print_partition(str, query_type);
print_order(str, query_type);
if (window_frame)
window_frame->print(str, query_type);
str->append(')');
}
void
Window_spec::print_partition(String *str, enum_query_type query_type)
{
if (partition_list->first)
{
str->append(STRING_WITH_LEN(" partition by "));
st_select_lex::print_order(str, partition_list->first, query_type);
}
}
void
Window_spec::print_order(String *str, enum_query_type query_type)
{
if (order_list->first)
{
str->append(STRING_WITH_LEN(" order by "));
st_select_lex::print_order(str, order_list->first, query_type);
}
}
bool
Window_frame::check_frame_bounds()
{
if ((top_bound->is_unbounded() &&
top_bound->precedence_type == Window_frame_bound::FOLLOWING) ||
(bottom_bound->is_unbounded() &&
bottom_bound->precedence_type == Window_frame_bound::PRECEDING) ||
(top_bound->precedence_type == Window_frame_bound::CURRENT &&
bottom_bound->precedence_type == Window_frame_bound::PRECEDING) ||
(bottom_bound->precedence_type == Window_frame_bound::CURRENT &&
top_bound->precedence_type == Window_frame_bound::FOLLOWING))
{
my_error(ER_BAD_COMBINATION_OF_WINDOW_FRAME_BOUND_SPECS, MYF(0));
return true;
}
return false;
}
void
Window_frame::print(String *str, enum_query_type query_type)
{
switch (units) {
case UNITS_ROWS:
str->append(STRING_WITH_LEN(" rows "));
break;
case UNITS_RANGE:
str->append(STRING_WITH_LEN(" range "));
break;
default:
DBUG_ASSERT(0);
}
str->append(STRING_WITH_LEN("between "));
top_bound->print(str, query_type);
str->append(STRING_WITH_LEN(" and "));
bottom_bound->print(str, query_type);
if (exclusion != EXCL_NONE)
{
str->append(STRING_WITH_LEN(" exclude "));
switch (exclusion) {
case EXCL_CURRENT_ROW:
str->append(STRING_WITH_LEN(" current row "));
break;
case EXCL_GROUP:
str->append(STRING_WITH_LEN(" group "));
break;
case EXCL_TIES:
str->append(STRING_WITH_LEN(" ties "));
break;
default:
DBUG_ASSERT(0);
;
}
}
}
void
Window_frame_bound::print(String *str, enum_query_type query_type)
{
if (precedence_type == CURRENT)
{
str->append(STRING_WITH_LEN(" current row "));
return;
}
if (is_unbounded())
str->append(STRING_WITH_LEN(" unbounded "));
else
offset->print(str ,query_type);
switch (precedence_type) {
case PRECEDING:
str->append(STRING_WITH_LEN(" preceding "));
break;
case FOLLOWING:
str->append(STRING_WITH_LEN(" following "));
break;
default:
DBUG_ASSERT(0);
}
}
/*
Setup window functions in a select
*/
int
setup_windows(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables,
List<Item> &fields, List<Item> &all_fields,
List<Window_spec> &win_specs, List<Item_window_func> &win_funcs)
{
Window_spec *win_spec;
DBUG_ENTER("setup_windows");
List_iterator<Window_spec> it(win_specs);
if (!thd->lex->current_select->is_win_spec_list_built)
{
/*
Move all unnamed specifications after the named ones.
We could have avoided it if we had built two separate lists for
named and unnamed specifications.
*/
Query_arena *arena, backup;
arena= thd->activate_stmt_arena_if_needed(&backup);
uint i = 0;
uint elems= win_specs.elements;
while ((win_spec= it++) && i++ < elems)
{
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
if (win_spec->name().str == NULL)
{
it.remove();
win_specs.push_back(win_spec);
}
}
if (arena)
thd->restore_active_arena(arena, &backup);
it.rewind();
thd->lex->current_select->is_win_spec_list_built= true;
}
List_iterator_fast<Window_spec> itp(win_specs);
while ((win_spec= it++))
{
bool hidden_group_fields;
if (win_spec->check_window_names(itp) ||
setup_group(thd, ref_pointer_array, tables, fields, all_fields,
win_spec->partition_list->first, &hidden_group_fields,
true) ||
setup_order(thd, ref_pointer_array, tables, fields, all_fields,
win_spec->order_list->first, true) ||
(win_spec->window_frame &&
win_spec->window_frame->check_frame_bounds()))
{
DBUG_RETURN(1);
}
if (win_spec->window_frame &&
win_spec->window_frame->exclusion != Window_frame::EXCL_NONE)
{
my_error(ER_FRAME_EXCLUSION_NOT_SUPPORTED, MYF(0));
DBUG_RETURN(1);
}
/*
For "win_func() OVER (ORDER BY order_list RANGE BETWEEN ...)",
2025-03-09 18:19:33 +02:00
- ORDER BY order_list must not be omitted
- the list must have a single element.
But it really only matters if the frame is bounded.
*/
if (win_spec->window_frame &&
win_spec->window_frame->units == Window_frame::UNITS_RANGE &&
!(win_spec->window_frame->top_bound->is_unbounded() &&
win_spec->window_frame->bottom_bound->is_unbounded()))
{
if (win_spec->order_list->elements != 1)
{
my_error(ER_RANGE_FRAME_NEEDS_SIMPLE_ORDERBY, MYF(0));
DBUG_RETURN(1);
}
/*
"The declared type of SK shall be numeric, datetime, or interval"
we don't support datetime or interval, yet.
*/
Item_result rtype= win_spec->order_list->first->item[0]->result_type();
if (rtype != REAL_RESULT && rtype != INT_RESULT &&
rtype != DECIMAL_RESULT)
{
my_error(ER_WRONG_TYPE_FOR_RANGE_FRAME, MYF(0));
DBUG_RETURN(1);
}
/*
"The declared type of UVS shall be numeric if the declared type of SK
is numeric; otherwise, it shall be an interval type that may be added
to or subtracted from the declared type of SK"
*/
Window_frame_bound *bounds[]= {win_spec->window_frame->top_bound,
win_spec->window_frame->bottom_bound,
NULL};
for (Window_frame_bound **pbound= &bounds[0]; *pbound; pbound++)
{
if (!(*pbound)->is_unbounded() &&
((*pbound)->precedence_type == Window_frame_bound::FOLLOWING ||
(*pbound)->precedence_type == Window_frame_bound::PRECEDING))
{
Item_result rtype= (*pbound)->offset->result_type();
if (rtype != REAL_RESULT && rtype != INT_RESULT &&
rtype != DECIMAL_RESULT)
{
my_error(ER_WRONG_TYPE_FOR_RANGE_FRAME, MYF(0));
DBUG_RETURN(1);
}
}
}
}
/* "ROWS PRECEDING|FOLLOWING $n" must have a numeric $n */
if (win_spec->window_frame &&
win_spec->window_frame->units == Window_frame::UNITS_ROWS)
{
Window_frame_bound *bounds[]= {win_spec->window_frame->top_bound,
win_spec->window_frame->bottom_bound,
NULL};
for (Window_frame_bound **pbound= &bounds[0]; *pbound; pbound++)
{
if (!(*pbound)->is_unbounded() &&
((*pbound)->precedence_type == Window_frame_bound::FOLLOWING ||
(*pbound)->precedence_type == Window_frame_bound::PRECEDING))
{
Item *offset= (*pbound)->offset;
if (offset->result_type() != INT_RESULT)
{
my_error(ER_WRONG_TYPE_FOR_ROWS_FRAME, MYF(0));
DBUG_RETURN(1);
}
}
}
}
}
List_iterator_fast<Item_window_func> li(win_funcs);
2018-01-04 09:22:59 +02:00
while (Item_window_func * win_func_item= li++)
{
if (win_func_item->check_result_type_of_order_item())
DBUG_RETURN(1);
}
DBUG_RETURN(0);
}
/**
@brief
Find fields common for all partition lists used in window functions
@param thd The thread handle
@details
This function looks for the field references in the partition lists
of all window functions used in this select that are common for
all the partition lists. The function returns an ORDER list contained
all such references.The list either is specially built by the function
or is taken directly from the first window specification.
@retval
pointer to the first element of the ORDER list contained field
references common for all partition lists
0 if no such reference is found.
*/
ORDER *st_select_lex::find_common_window_func_partition_fields(THD *thd)
{
ORDER *ord;
Item *item;
DBUG_ASSERT(window_funcs.elements);
List_iterator_fast<Item_window_func> it(window_funcs);
Item_window_func *first_wf= it++;
if (!first_wf->window_spec->partition_list)
return 0;
List<Item> common_fields;
uint first_partition_elements= 0;
for (ord= first_wf->window_spec->partition_list->first; ord; ord= ord->next)
{
if ((*ord->item)->real_item()->type() == Item::FIELD_ITEM)
common_fields.push_back(*ord->item, thd->mem_root);
first_partition_elements++;
}
if (window_specs.elements == 1 &&
common_fields.elements == first_partition_elements)
return first_wf->window_spec->partition_list->first;
List_iterator<Item> li(common_fields);
Item_window_func *wf;
while (common_fields.elements && (wf= it++))
{
if (!wf->window_spec->partition_list)
return 0;
while ((item= li++))
{
for (ord= wf->window_spec->partition_list->first; ord; ord= ord->next)
{
if (item->eq(*ord->item, false))
break;
}
if (!ord)
li.remove();
}
li.rewind();
}
if (!common_fields.elements)
return 0;
if (common_fields.elements == first_partition_elements)
return first_wf->window_spec->partition_list->first;
SQL_I_List<ORDER> res_list;
for (ord= first_wf->window_spec->partition_list->first, item= li++;
ord; ord= ord->next)
{
if (item != *ord->item)
continue;
if (add_to_list(thd, res_list, item, ord->direction))
return 0;
item= li++;
}
return res_list.first;
}
/////////////////////////////////////////////////////////////////////////////
// Sorting window functions to minimize the number of table scans
// performed during the computation of these functions
/////////////////////////////////////////////////////////////////////////////
#define CMP_LT -2 // Less than
#define CMP_LT_C -1 // Less than and compatible
#define CMP_EQ 0 // Equal to
#define CMP_GT_C 1 // Greater than and compatible
#define CMP_GT 2 // Greater then
2021-09-17 14:53:54 +03:00
/*
This function is used for sorting ORDER/PARTITION BY clauses of window
functions and so must implement an order relation on ORDER BY clauses"
It is called by a sorting function.
The function return's CMP_EQ (=0) if the values are identical.
If not equal, it returns a stable value < or > than 0.
*/
static
int compare_order_elements(ORDER *ord1, int weight1,
ORDER *ord2, int weight2)
{
if (*ord1->item == *ord2->item && ord1->direction == ord2->direction)
return CMP_EQ;
Item *item1= (*ord1->item)->real_item();
Item *item2= (*ord2->item)->real_item();
bool item1_field= (item1->type() == Item::FIELD_ITEM);
bool item2_field= (item2->type() == Item::FIELD_ITEM);
ptrdiff_t cmp;
if (item1_field && item2_field)
{
DBUG_ASSERT(((Item_field *) item1)->field->table ==
((Item_field *) item2)->field->table);
cmp= ((Item_field *) item1)->field->field_index -
((Item_field *) item2)->field->field_index;
}
else if (item1_field && !item2_field)
return CMP_LT;
else if (!item1_field && item2_field)
return CMP_LT;
else
{
/*
Ok, item1_field==NULL and item2_field==NULL.
We're not able to compare Item expressions. Order them according to
their passed "weight" (which comes from Window_spec::win_spec_number):
*/
if (weight1 != weight2)
cmp= weight1 - weight2;
else
{
/*
The weight is the same. That is, the elements come from the same
window specification... This shouldn't happen.
*/
DBUG_ASSERT(0);
cmp= item1 - item2;
}
}
if (cmp == 0)
{
if (ord1->direction == ord2->direction)
return CMP_EQ;
return ord1->direction > ord2->direction ? CMP_GT : CMP_LT;
}
else
return cmp > 0 ? CMP_GT : CMP_LT;
}
static
int compare_order_lists(SQL_I_List<ORDER> *part_list1,
int spec_number1,
SQL_I_List<ORDER> *part_list2,
int spec_number2)
{
if (part_list1 == part_list2)
return CMP_EQ;
ORDER *elem1= part_list1->first;
ORDER *elem2= part_list2->first;
for ( ; elem1 && elem2; elem1= elem1->next, elem2= elem2->next)
{
int cmp;
// remove all constants as we don't need them for comparision
while(elem1 && ((*elem1->item)->real_item())->const_item())
{
elem1= elem1->next;
continue;
}
while(elem2 && ((*elem2->item)->real_item())->const_item())
{
elem2= elem2->next;
continue;
}
if (!elem1 || !elem2)
break;
if ((cmp= compare_order_elements(elem1, spec_number1,
elem2, spec_number2)))
return cmp;
}
if (elem1)
return CMP_GT_C;
if (elem2)
return CMP_LT_C;
return CMP_EQ;
}
static
int compare_window_frame_bounds(Window_frame_bound *win_frame_bound1,
Window_frame_bound *win_frame_bound2,
bool is_bottom_bound)
{
int res;
if (win_frame_bound1->precedence_type != win_frame_bound2->precedence_type)
{
res= win_frame_bound1->precedence_type > win_frame_bound2->precedence_type ?
CMP_GT : CMP_LT;
if (is_bottom_bound)
res= -res;
return res;
}
if (win_frame_bound1->is_unbounded() && win_frame_bound2->is_unbounded())
return CMP_EQ;
if (!win_frame_bound1->is_unbounded() && !win_frame_bound2->is_unbounded())
{
if (win_frame_bound1->offset->eq(win_frame_bound2->offset, true))
return CMP_EQ;
else
{
Changing field::field_name and Item::name to LEX_CSTRING Benefits of this patch: - Removed a lot of calls to strlen(), especially for field_string - Strings generated by parser are now const strings, less chance of accidently changing a string - Removed a lot of calls with LEX_STRING as parameter (changed to pointer) - More uniform code - Item::name_length was not kept up to date. Now fixed - Several bugs found and fixed (Access to null pointers, access of freed memory, wrong arguments to printf like functions) - Removed a lot of casts from (const char*) to (char*) Changes: - This caused some ABI changes - lex_string_set now uses LEX_CSTRING - Some fucntions are now taking const char* instead of char* - Create_field::change and after changed to LEX_CSTRING - handler::connect_string, comment and engine_name() changed to LEX_CSTRING - Checked printf() related calls to find bugs. Found and fixed several errors in old code. - A lot of changes from LEX_STRING to LEX_CSTRING, especially related to parsing and events. - Some changes from LEX_STRING and LEX_STRING & to LEX_CSTRING* - Some changes for char* to const char* - Added printf argument checking for my_snprintf() - Introduced null_clex_str, star_clex_string, temp_lex_str to simplify code - Added item_empty_name and item_used_name to be able to distingush between items that was given an empty name and items that was not given a name This is used in sql_yacc.yy to know when to give an item a name. - select table_name."*' is not anymore same as table_name.* - removed not used function Item::rename() - Added comparision of item->name_length before some calls to my_strcasecmp() to speed up comparison - Moved Item_sp_variable::make_field() from item.h to item.cc - Some minimal code changes to avoid copying to const char * - Fixed wrong error message in wsrep_mysql_parse() - Fixed wrong code in find_field_in_natural_join() where real_item() was set when it shouldn't - ER_ERROR_ON_RENAME was used with extra arguments. - Removed some (wrong) ER_OUTOFMEMORY, as alloc_root will already give the error. TODO: - Check possible unsafe casts in plugin/auth_examples/qa_auth_interface.c - Change code to not modify LEX_CSTRING for database name (as part of lower_case_table_names)
2017-04-23 19:39:57 +03:00
res= strcmp(win_frame_bound1->offset->name.str,
win_frame_bound2->offset->name.str);
res= res > 0 ? CMP_GT : CMP_LT;
if (is_bottom_bound)
res= -res;
return res;
}
}
/*
Here we have:
win_frame_bound1->is_unbounded() != win_frame_bound1->is_unbounded()
*/
return is_bottom_bound != win_frame_bound1->is_unbounded() ? CMP_LT : CMP_GT;
}
static
int compare_window_frames(Window_frame *win_frame1,
Window_frame *win_frame2)
{
int cmp;
if (win_frame1 == win_frame2)
return CMP_EQ;
if (!win_frame1)
return CMP_LT;
if (!win_frame2)
return CMP_GT;
if (win_frame1->units != win_frame2->units)
return win_frame1->units > win_frame2->units ? CMP_GT : CMP_LT;
cmp= compare_window_frame_bounds(win_frame1->top_bound,
win_frame2->top_bound,
false);
if (cmp)
return cmp;
cmp= compare_window_frame_bounds(win_frame1->bottom_bound,
win_frame2->bottom_bound,
true);
if (cmp)
return cmp;
if (win_frame1->exclusion != win_frame2->exclusion)
return win_frame1->exclusion > win_frame2->exclusion ? CMP_GT_C : CMP_LT_C;
return CMP_EQ;
}
static
int compare_window_spec_joined_lists(Window_spec *win_spec1,
Window_spec *win_spec2)
{
win_spec1->join_partition_and_order_lists();
win_spec2->join_partition_and_order_lists();
int cmp= compare_order_lists(win_spec1->partition_list,
win_spec1->win_spec_number,
win_spec2->partition_list,
win_spec2->win_spec_number);
win_spec1->disjoin_partition_and_order_lists();
win_spec2->disjoin_partition_and_order_lists();
return cmp;
}
static
int compare_window_funcs_by_window_specs(Item_window_func *win_func1,
Item_window_func *win_func2,
void *arg)
{
int cmp;
Window_spec *win_spec1= win_func1->window_spec;
Window_spec *win_spec2= win_func2->window_spec;
if (win_spec1 == win_spec2)
return CMP_EQ;
cmp= compare_order_lists(win_spec1->partition_list,
win_spec1->win_spec_number,
win_spec2->partition_list,
win_spec2->win_spec_number);
if (cmp == CMP_EQ)
{
/*
Partition lists contain the same elements.
Let's use only one of the lists.
*/
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
if (!win_spec1->name().str && win_spec2->name().str)
{
win_spec1->save_partition_list= win_spec1->partition_list;
win_spec1->partition_list= win_spec2->partition_list;
}
else
{
win_spec2->save_partition_list= win_spec2->partition_list;
win_spec2->partition_list= win_spec1->partition_list;
}
cmp= compare_order_lists(win_spec1->order_list,
win_spec1->win_spec_number,
win_spec2->order_list,
win_spec2->win_spec_number);
if (cmp != CMP_EQ)
return cmp;
/*
Order lists contain the same elements.
Let's use only one of the lists.
*/
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
if (!win_spec1->name().str && win_spec2->name().str)
{
win_spec1->save_order_list= win_spec2->order_list;
win_spec1->order_list= win_spec2->order_list;
}
else
{
win_spec1->save_order_list= win_spec2->order_list;
win_spec2->order_list= win_spec1->order_list;
}
cmp= compare_window_frames(win_spec1->window_frame,
win_spec2->window_frame);
if (cmp != CMP_EQ)
return cmp;
/* Window frames are equal. Let's use only one of them. */
MDEV-31340 Remove MY_COLLATION_HANDLER::strcasecmp() This patch also fixes: MDEV-33050 Build-in schemas like oracle_schema are accent insensitive MDEV-33084 LASTVAL(t1) and LASTVAL(T1) do not work well with lower-case-table-names=0 MDEV-33085 Tables T1 and t1 do not work well with ENGINE=CSV and lower-case-table-names=0 MDEV-33086 SHOW OPEN TABLES IN DB1 -- is case insensitive with lower-case-table-names=0 MDEV-33088 Cannot create triggers in the database `MYSQL` MDEV-33103 LOCK TABLE t1 AS t2 -- alias is not case sensitive with lower-case-table-names=0 MDEV-33109 DROP DATABASE MYSQL -- does not drop SP with lower-case-table-names=0 MDEV-33110 HANDLER commands are case insensitive with lower-case-table-names=0 MDEV-33119 User is case insensitive in INFORMATION_SCHEMA.VIEWS MDEV-33120 System log table names are case insensitive with lower-cast-table-names=0 - Removing the virtual function strnncoll() from MY_COLLATION_HANDLER - Adding a wrapper function CHARSET_INFO::streq(), to compare two strings for equality. For now it calls strnncoll() internally. In the future it will turn into a virtual function. - Adding new accent sensitive case insensitive collations: - utf8mb4_general1400_as_ci - utf8mb3_general1400_as_ci They implement accent sensitive case insensitive comparison. The weight of a character is equal to the code point of its upper case variant. These collations use Unicode-14.0.0 casefolding data. The result of my_charset_utf8mb3_general1400_as_ci.strcoll() is very close to the former my_charset_utf8mb3_general_ci.strcasecmp() There is only a difference in a couple dozen rare characters, because: - the switch from "tolower" to "toupper" comparison, to make utf8mb3_general1400_as_ci closer to utf8mb3_general_ci - the switch from Unicode-3.0.0 to Unicode-14.0.0 This difference should be tolarable. See the list of affected characters in the MDEV description. Note, utf8mb4_general1400_as_ci correctly handles non-BMP characters! Unlike utf8mb4_general_ci, it does not treat all BMP characters as equal. - Adding classes representing names of the file based database objects: Lex_ident_db Lex_ident_table Lex_ident_trigger Their comparison collation depends on the underlying file system case sensitivity and on --lower-case-table-names and can be either my_charset_bin or my_charset_utf8mb3_general1400_as_ci. - Adding classes representing names of other database objects, whose names have case insensitive comparison style, using my_charset_utf8mb3_general1400_as_ci: Lex_ident_column Lex_ident_sys_var Lex_ident_user_var Lex_ident_sp_var Lex_ident_ps Lex_ident_i_s_table Lex_ident_window Lex_ident_func Lex_ident_partition Lex_ident_with_element Lex_ident_rpl_filter Lex_ident_master_info Lex_ident_host Lex_ident_locale Lex_ident_plugin Lex_ident_engine Lex_ident_server Lex_ident_savepoint Lex_ident_charset engine_option_value::Name - All the mentioned Lex_ident_xxx classes implement a method streq(): if (ident1.streq(ident2)) do_equal(); This method works as a wrapper for CHARSET_INFO::streq(). - Changing a lot of "LEX_CSTRING name" to "Lex_ident_xxx name" in class members and in function/method parameters. - Replacing all calls like system_charset_info->coll->strcasecmp(ident1, ident2) to ident1.streq(ident2) - Taking advantage of the c++11 user defined literal operator for LEX_CSTRING (see m_strings.h) and Lex_ident_xxx (see lex_ident.h) data types. Use example: const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column; is now a shorter version of: const Lex_ident_column primary_key_name= Lex_ident_column({STRING_WITH_LEN("PRIMARY")});
2023-04-26 15:27:01 +04:00
if (!win_spec1->name().str && win_spec2->name().str)
win_spec1->window_frame= win_spec2->window_frame;
else
win_spec2->window_frame= win_spec1->window_frame;
return CMP_EQ;
}
if (cmp == CMP_GT || cmp == CMP_LT)
return cmp;
/* one of the partitions lists is the proper beginning of the another */
cmp= compare_window_spec_joined_lists(win_spec1, win_spec2);
if (CMP_LT_C <= cmp && cmp <= CMP_GT_C)
cmp= win_spec1->partition_list->elements <
win_spec2->partition_list->elements ? CMP_GT_C : CMP_LT_C;
return cmp;
}
typedef int (*Item_window_func_cmp)(Item_window_func *f1,
Item_window_func *f2,
void *arg);
/*
@brief
Sort window functions so that those that can be computed together are
adjacent.
@detail
Sort window functions by their
- required sorting order,
- partition list,
- window frame compatibility.
The changes between the groups are marked by setting item_window_func->marker.
*/
static
void order_window_funcs_by_window_specs(List<Item_window_func> *win_func_list)
{
if (win_func_list->elements == 0)
return;
bubble_sort<Item_window_func>(win_func_list,
compare_window_funcs_by_window_specs,
NULL);
List_iterator_fast<Item_window_func> it(*win_func_list);
Item_window_func *prev= it++;
prev->marker= (MARKER_SORTORDER_CHANGE |
MARKER_PARTITION_CHANGE |
MARKER_FRAME_CHANGE);
Item_window_func *curr;
while ((curr= it++))
{
Window_spec *win_spec_prev= prev->window_spec;
Window_spec *win_spec_curr= curr->window_spec;
curr->marker= MARKER_UNUSED;
if (!(win_spec_prev->partition_list == win_spec_curr->partition_list &&
win_spec_prev->order_list == win_spec_curr->order_list))
{
int cmp;
if (win_spec_prev->partition_list == win_spec_curr->partition_list)
cmp= compare_order_lists(win_spec_prev->order_list,
win_spec_prev->win_spec_number,
win_spec_curr->order_list,
win_spec_curr->win_spec_number);
else
cmp= compare_window_spec_joined_lists(win_spec_prev, win_spec_curr);
if (!(CMP_LT_C <= cmp && cmp <= CMP_GT_C))
{
curr->marker= (MARKER_SORTORDER_CHANGE |
MARKER_PARTITION_CHANGE |
MARKER_FRAME_CHANGE);
}
else if (win_spec_prev->partition_list != win_spec_curr->partition_list)
{
curr->marker|= MARKER_PARTITION_CHANGE | MARKER_FRAME_CHANGE;
}
}
else if (win_spec_prev->window_frame != win_spec_curr->window_frame)
curr->marker|= MARKER_FRAME_CHANGE;
prev= curr;
}
}
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// Window Frames support
/////////////////////////////////////////////////////////////////////////////
// note: make rr_from_pointers static again when not need it here anymore
int rr_from_pointers(READ_RECORD *info);
/////////////////////////////////////////////////////////////////////////////
/*
A cursor over a sequence of rowids. One can
- Move to next rowid
- jump to given number in the sequence
- Know the number of the current rowid (i.e. how many rowids have been read)
*/
class Rowid_seq_cursor
{
public:
Rowid_seq_cursor() : io_cache(NULL), ref_buffer(0) {}
virtual ~Rowid_seq_cursor()
{
if (ref_buffer)
my_free(ref_buffer);
if (io_cache)
{
end_slave_io_cache(io_cache);
my_free(io_cache);
io_cache= NULL;
}
}
private:
/* Length of one rowid element */
size_t ref_length;
/* If io_cache=!NULL, use it */
IO_CACHE *io_cache;
uchar *ref_buffer; /* Buffer for the last returned rowid */
ha_rows rownum; /* Number of the rowid that is about to be returned */
ha_rows current_ref_buffer_rownum;
bool ref_buffer_valid;
/* The following are used when we are reading from an array of pointers */
uchar *cache_start;
uchar *cache_pos;
uchar *cache_end;
public:
void init(READ_RECORD *info)
{
ref_length= info->ref_length;
if (info->read_record_func == rr_from_pointers)
{
io_cache= NULL;
cache_start= info->cache_pos;
cache_pos= info->cache_pos;
cache_end= info->cache_end;
}
else
{
//DBUG_ASSERT(info->read_record == rr_from_tempfile);
rownum= 0;
io_cache= (IO_CACHE*)my_malloc(PSI_INSTRUMENT_ME, sizeof(IO_CACHE), MYF(0));
init_slave_io_cache(info->io_cache, io_cache);
ref_buffer= (uchar*)my_malloc(PSI_INSTRUMENT_ME, ref_length, MYF(0));
ref_buffer_valid= false;
}
}
virtual int next()
{
/* Allow multiple next() calls in EOF state. */
if (at_eof())
return -1;
if (io_cache)
{
rownum++;
}
else
{
cache_pos+= ref_length;
DBUG_ASSERT(cache_pos <= cache_end);
}
return 0;
}
virtual int prev()
{
if (io_cache)
{
if (rownum == 0)
return -1;
rownum--;
return 0;
}
else
{
/* Allow multiple prev() calls when positioned at the start. */
if (cache_pos == cache_start)
return -1;
cache_pos-= ref_length;
DBUG_ASSERT(cache_pos >= cache_start);
return 0;
}
}
ha_rows get_rownum() const
{
if (io_cache)
return rownum;
else
return (cache_pos - cache_start) / ref_length;
}
void move_to(ha_rows row_number)
{
if (io_cache)
{
rownum= row_number;
}
else
{
cache_pos= MY_MIN(cache_end, cache_start + row_number * ref_length);
DBUG_ASSERT(cache_pos <= cache_end);
}
}
protected:
bool at_eof()
{
if (io_cache)
{
return rownum * ref_length >= io_cache->end_of_file;
}
else
return (cache_pos == cache_end);
}
bool get_curr_rowid(uchar **row_id)
{
if (io_cache)
{
DBUG_ASSERT(!at_eof());
if (!ref_buffer_valid || current_ref_buffer_rownum != rownum)
{
seek_io_cache(io_cache, rownum * ref_length);
if (my_b_read(io_cache,ref_buffer,ref_length))
{
/* Error reading from file. */
return true;
}
ref_buffer_valid= true;
current_ref_buffer_rownum = rownum;
}
*row_id = ref_buffer;
return false;
}
else
{
*row_id= cache_pos;
return false;
}
}
};
/*
Cursor which reads from rowid sequence and also retrieves table rows.
*/
class Table_read_cursor : public Rowid_seq_cursor
{
public:
~Table_read_cursor() override = default;
void init(READ_RECORD *info)
{
Rowid_seq_cursor::init(info);
table= info->table;
record= info->record();
}
virtual int fetch()
{
if (at_eof())
return -1;
uchar* curr_rowid;
if (get_curr_rowid(&curr_rowid))
return -1;
return table->file->ha_rnd_pos(record, curr_rowid);
}
private:
2025-03-09 18:19:33 +02:00
/* The table that is accessed by this cursor. */
TABLE *table;
/* Buffer where to store the table's record data. */
uchar *record;
// TODO(spetrunia): should move_to() also read row here?
};
/*
A cursor which only moves within a partition. The scan stops at the partition
end, and it needs an explicit command to move to the next partition.
This cursor can not move backwards.
*/
class Partition_read_cursor : public Table_read_cursor
{
public:
Partition_read_cursor(THD *thd, SQL_I_List<ORDER> *partition_list) :
bound_tracker(thd, partition_list) {}
void init(READ_RECORD *info)
{
Table_read_cursor::init(info);
bound_tracker.init();
end_of_partition= false;
}
/*
Informs the cursor that we need to move into the next partition.
The next partition is provided in two ways:
- in table->record[0]..
- rownum parameter has the row number.
*/
void on_next_partition(ha_rows rownum)
{
/* Remember the sort key value from the new partition */
move_to(rownum);
bound_tracker.check_if_next_group();
end_of_partition= false;
}
/*
This returns -1 when end of partition was reached.
*/
int next() override
{
int res;
if (end_of_partition)
return -1;
if ((res= Table_read_cursor::next()) ||
(res= fetch()))
{
/* TODO(cvicentiu) This does not consider table read failures.
Perhaps assuming end of table like this is fine in that case. */
/* This row is the final row in the table. To maintain semantics
that cursors always point to the last valid row, move back one step,
but mark end_of_partition as true. */
Table_read_cursor::prev();
end_of_partition= true;
return res;
}
if (bound_tracker.compare_with_cache())
{
/* This row is part of a new partition, don't move
forward any more untill we get informed of a new partition. */
Table_read_cursor::prev();
end_of_partition= true;
return -1;
}
return 0;
}
private:
Group_bound_tracker bound_tracker;
bool end_of_partition;
};
/////////////////////////////////////////////////////////////////////////////
/*
Window frame bound cursor. Abstract interface.
@detail
The cursor moves within the partition that the current row is in.
It may be ahead or behind the current row.
The cursor also assumes that the current row moves forward through the
partition and will move to the next adjacent partition after this one.
List of all cursor classes:
Frame_cursor
Frame_range_n_top
Frame_range_n_bottom
Frame_range_current_row_top
Frame_range_current_row_bottom
Frame_n_rows_preceding
Frame_n_rows_following
Frame_rows_current_row_top = Frame_n_rows_preceding(0)
Frame_rows_current_row_bottom
// These handle both RANGE and ROWS-type bounds
Frame_unbounded_preceding
Frame_unbounded_following
// This is not used as a frame bound, it counts rows in the partition:
Frame_unbounded_following_set_count : public Frame_unbounded_following
@todo
- if we want to allocate this on the MEM_ROOT we should make sure
it is not re-allocated for every subquery execution.
*/
class Frame_cursor : public Sql_alloc
{
public:
Frame_cursor() : sum_functions(), perform_no_action(false) {}
virtual void init(READ_RECORD *info) {};
bool add_sum_func(Item_sum* item)
{
return sum_functions.push_back(item);
}
/*
Current row has moved to the next partition and is positioned on the first
row there. Position the frame bound accordingly.
@param first - TRUE means this is the first partition
@param item - Put or remove rows from there.
@detail
- if first==false, the caller guarantees that tbl->record[0] points at the
first row in the new partition.
- if first==true, we are just starting in the first partition and no such
guarantee is provided.
- The callee may move tbl->file and tbl->record[0] to point to some other
row.
*/
virtual void pre_next_partition(ha_rows rownum) {};
virtual void next_partition(ha_rows rownum)=0;
/*
The current row has moved one row forward.
Move this frame bound accordingly, and update the value of aggregate
function as necessary.
*/
virtual void pre_next_row() {};
virtual void next_row()=0;
virtual bool is_outside_computation_bounds() const { return false; };
virtual ~Frame_cursor() = default;
/*
Regular frame cursors add or remove values from the sum functions they
manage. By calling this method, they will only perform the required
movement within the table, but no adding/removing will happen.
*/
void set_no_action()
{
perform_no_action= true;
}
/* Retrieves the row number that this cursor currently points at. */
virtual ha_rows get_curr_rownum() const= 0;
protected:
inline void add_value_to_items()
{
if (perform_no_action)
return;
List_iterator_fast<Item_sum> it(sum_functions);
Item_sum *item_sum;
while ((item_sum= it++))
{
item_sum->add();
}
}
inline void remove_value_from_items()
{
if (perform_no_action)
return;
List_iterator_fast<Item_sum> it(sum_functions);
Item_sum *item_sum;
while ((item_sum= it++))
{
item_sum->remove();
}
}
/* Clear all sum functions handled by this cursor. */
void clear_sum_functions()
{
List_iterator_fast<Item_sum> iter_sum_func(sum_functions);
Item_sum *sum_func;
while ((sum_func= iter_sum_func++))
{
sum_func->clear();
}
}
/* Sum functions that this cursor handles. */
List<Item_sum> sum_functions;
private:
bool perform_no_action;
};
/*
A class that owns cursor objects associated with a specific window function.
*/
class Cursor_manager
{
public:
bool add_cursor(Frame_cursor *cursor)
{
return cursors.push_back(cursor);
}
void initialize_cursors(READ_RECORD *info)
{
List_iterator_fast<Frame_cursor> iter(cursors);
Frame_cursor *fc;
while ((fc= iter++))
fc->init(info);
}
void notify_cursors_partition_changed(ha_rows rownum)
{
List_iterator_fast<Frame_cursor> iter(cursors);
Frame_cursor *cursor;
while ((cursor= iter++))
cursor->pre_next_partition(rownum);
iter.rewind();
while ((cursor= iter++))
cursor->next_partition(rownum);
}
void notify_cursors_next_row()
{
List_iterator_fast<Frame_cursor> iter(cursors);
Frame_cursor *cursor;
while ((cursor= iter++))
cursor->pre_next_row();
iter.rewind();
while ((cursor= iter++))
cursor->next_row();
}
~Cursor_manager() { cursors.delete_elements(); }
private:
/* List of the cursors that this manager owns. */
List<Frame_cursor> cursors;
};
2016-03-11 23:29:52 +03:00
//////////////////////////////////////////////////////////////////////////////
// RANGE-type frames
2016-03-11 23:29:52 +03:00
//////////////////////////////////////////////////////////////////////////////
/*
Frame_range_n_top handles the top end of RANGE-type frame.
That is, it handles:
RANGE BETWEEN n PRECEDING AND ...
RANGE BETWEEN n FOLLOWING AND ...
Top of the frame doesn't need to check for partition end, since bottom will
reach it before.
*/
class Frame_range_n_top : public Frame_cursor
{
Partition_read_cursor cursor;
Cached_item_item *range_expr;
Item *n_val;
Item *item_add;
const bool is_preceding;
bool end_of_partition;
/*
1 when order_list uses ASC ordering
-1 when order_list uses DESC ordering
*/
int order_direction;
public:
Frame_range_n_top(THD *thd,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list,
bool is_preceding_arg, Item *n_val_arg) :
cursor(thd, partition_list), n_val(n_val_arg), item_add(NULL),
is_preceding(is_preceding_arg)
{
DBUG_ASSERT(order_list->elements == 1);
Item *src_expr= order_list->first->item[0];
if (order_list->first->direction == ORDER::ORDER_ASC)
order_direction= 1;
else
order_direction= -1;
range_expr= (Cached_item_item*) new_Cached_item(thd, src_expr, FALSE);
bool use_minus= is_preceding;
if (order_direction == -1)
use_minus= !use_minus;
if (use_minus)
item_add= new (thd->mem_root) Item_func_minus(thd, src_expr, n_val);
else
item_add= new (thd->mem_root) Item_func_plus(thd, src_expr, n_val);
item_add->fix_fields(thd, &item_add);
}
void init(READ_RECORD *info) override
{
cursor.init(info);
}
void pre_next_partition(ha_rows rownum) override
{
// Save the value of FUNC(current_row)
range_expr->fetch_value_from(item_add);
cursor.on_next_partition(rownum);
end_of_partition= false;
}
void next_partition(ha_rows rownum) override
{
walk_till_non_peer();
}
void pre_next_row() override
{
if (end_of_partition)
return;
range_expr->fetch_value_from(item_add);
}
void next_row() override
{
if (end_of_partition)
return;
/*
Ok, our cursor is at the first row R where
(prev_row + n) >= R
We need to check about the current row.
*/
walk_till_non_peer();
}
ha_rows get_curr_rownum() const override
{
return cursor.get_rownum();
}
bool is_outside_computation_bounds() const override
{
if (end_of_partition)
return true;
return false;
}
private:
void walk_till_non_peer()
{
if (cursor.fetch()) // ERROR
return;
// Current row is not a peer.
if (order_direction * range_expr->cmp_read_only() <= 0)
return;
remove_value_from_items();
int res;
while (!(res= cursor.next()))
{
/* Note, no need to fetch the value explicitly here. The partition
read cursor will fetch it to check if the partition has changed.
TODO(cvicentiu) make this piece of information not necessary by
reimplementing Partition_read_cursor.
*/
if (order_direction * range_expr->cmp_read_only() <= 0)
break;
remove_value_from_items();
}
if (res)
end_of_partition= true;
}
};
/*
Frame_range_n_bottom handles bottom end of RANGE-type frame.
That is, it handles frame bounds in form:
RANGE BETWEEN ... AND n PRECEDING
RANGE BETWEEN ... AND n FOLLOWING
Bottom end moves first so it needs to check for partition end
(todo: unless it's PRECEDING and in that case it doesnt)
(todo: factor out common parts with Frame_range_n_top into
a common ancestor)
*/
class Frame_range_n_bottom: public Frame_cursor
{
Partition_read_cursor cursor;
Cached_item_item *range_expr;
Item *n_val;
Item *item_add;
const bool is_preceding;
bool end_of_partition;
/*
1 when order_list uses ASC ordering
-1 when order_list uses DESC ordering
*/
int order_direction;
public:
Frame_range_n_bottom(THD *thd,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list,
bool is_preceding_arg, Item *n_val_arg) :
cursor(thd, partition_list), n_val(n_val_arg), item_add(NULL),
is_preceding(is_preceding_arg), added_values(false)
{
DBUG_ASSERT(order_list->elements == 1);
Item *src_expr= order_list->first->item[0];
if (order_list->first->direction == ORDER::ORDER_ASC)
order_direction= 1;
else
order_direction= -1;
range_expr= (Cached_item_item*) new_Cached_item(thd, src_expr, FALSE);
bool use_minus= is_preceding;
if (order_direction == -1)
use_minus= !use_minus;
if (use_minus)
item_add= new (thd->mem_root) Item_func_minus(thd, src_expr, n_val);
else
item_add= new (thd->mem_root) Item_func_plus(thd, src_expr, n_val);
item_add->fix_fields(thd, &item_add);
}
void init(READ_RECORD *info) override
{
cursor.init(info);
}
void pre_next_partition(ha_rows rownum) override
{
// Save the value of FUNC(current_row)
range_expr->fetch_value_from(item_add);
cursor.on_next_partition(rownum);
end_of_partition= false;
added_values= false;
}
void next_partition(ha_rows rownum) override
{
cursor.move_to(rownum);
walk_till_non_peer();
}
void pre_next_row() override
{
if (end_of_partition)
return;
range_expr->fetch_value_from(item_add);
}
void next_row() override
{
if (end_of_partition)
return;
/*
Ok, our cursor is at the first row R where
(prev_row + n) >= R
We need to check about the current row.
*/
walk_till_non_peer();
}
bool is_outside_computation_bounds() const override
{
if (!added_values)
return true;
return false;
}
ha_rows get_curr_rownum() const override
{
if (end_of_partition)
return cursor.get_rownum(); // Cursor does not pass over partition bound.
else
return cursor.get_rownum() - 1; // Cursor is placed on first non peer.
}
private:
bool added_values;
void walk_till_non_peer()
{
cursor.fetch();
// Current row is not a peer.
if (order_direction * range_expr->cmp_read_only() < 0)
return;
add_value_to_items(); // Add current row.
added_values= true;
int res;
while (!(res= cursor.next()))
{
if (order_direction * range_expr->cmp_read_only() < 0)
break;
add_value_to_items();
}
if (res)
end_of_partition= true;
}
};
/*
2016-03-11 23:29:52 +03:00
RANGE BETWEEN ... AND CURRENT ROW, bottom frame bound for CURRENT ROW
...
| peer1
| peer2 <----- current_row
| peer3
2016-03-11 23:29:52 +03:00
+-peer4 <----- the cursor points here. peer4 itself is included.
nonpeer1
nonpeer2
2016-03-11 23:29:52 +03:00
This bound moves in front of the current_row. It should be a the first row
that is still a peer of the current row.
*/
class Frame_range_current_row_bottom: public Frame_cursor
{
Partition_read_cursor cursor;
Group_bound_tracker peer_tracker;
bool dont_move;
public:
Frame_range_current_row_bottom(THD *thd,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list) :
cursor(thd, partition_list), peer_tracker(thd, order_list)
{
}
void init(READ_RECORD *info) override
{
cursor.init(info);
peer_tracker.init();
}
void pre_next_partition(ha_rows rownum) override
{
// Save the value of the current_row
peer_tracker.check_if_next_group();
cursor.on_next_partition(rownum);
// Add the current row now because our cursor has already seen it
add_value_to_items();
}
void next_partition(ha_rows rownum) override
{
walk_till_non_peer();
}
void pre_next_row() override
{
dont_move= !peer_tracker.check_if_next_group();
}
2016-03-11 23:29:52 +03:00
void next_row() override
{
// Check if our cursor is pointing at a peer of the current row.
// If not, move forward until that becomes true
if (dont_move)
{
/*
Our current is not a peer of the current row.
No need to move the bound.
*/
return;
}
walk_till_non_peer();
}
ha_rows get_curr_rownum() const override
{
return cursor.get_rownum();
}
private:
void walk_till_non_peer()
{
/*
Walk forward until we've met first row that's not a peer of the current
row
*/
while (!cursor.next())
{
if (peer_tracker.compare_with_cache())
{
cursor.prev(); // Move to our peer.
break;
}
add_value_to_items();
}
}
};
/*
2016-03-11 23:29:52 +03:00
RANGE BETWEEN CURRENT ROW AND .... Top CURRENT ROW, RANGE-type frame bound
2016-03-11 23:29:52 +03:00
nonpeer1
nonpeer2
+-peer1 <----- the cursor points here. peer1 itself is included.
| peer2
| peer3 <----- current_row
| peer4
...
It moves behind the current_row. It is located right after the first peer of
the current_row.
*/
class Frame_range_current_row_top : public Frame_cursor
{
Group_bound_tracker bound_tracker;
Table_read_cursor cursor;
Group_bound_tracker peer_tracker;
bool move;
public:
Frame_range_current_row_top(THD *thd,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list) :
bound_tracker(thd, partition_list), cursor(), peer_tracker(thd, order_list),
move(false)
{}
void init(READ_RECORD *info) override
{
bound_tracker.init();
cursor.init(info);
peer_tracker.init();
}
void pre_next_partition(ha_rows rownum) override
{
// Fetch the value from the first row
peer_tracker.check_if_next_group();
cursor.move_to(rownum);
}
void next_partition(ha_rows rownum) override {}
void pre_next_row() override
{
// Check if the new current_row is a peer of the row that our cursor is
// pointing to.
move= peer_tracker.check_if_next_group();
}
void next_row() override
{
if (move)
{
/*
Our cursor is pointing at the first row that was a peer of the previous
current row. Or, it was the first row in the partition.
*/
if (cursor.fetch())
return;
// todo: need the following check ?
if (!peer_tracker.compare_with_cache())
return;
remove_value_from_items();
do
{
if (cursor.next() || cursor.fetch())
return;
if (!peer_tracker.compare_with_cache())
return;
remove_value_from_items();
}
while (1);
}
}
ha_rows get_curr_rownum() const override
{
return cursor.get_rownum();
}
};
2016-03-11 23:29:52 +03:00
/////////////////////////////////////////////////////////////////////////////
// UNBOUNDED frame bounds (shared between RANGE and ROWS)
/////////////////////////////////////////////////////////////////////////////
/*
UNBOUNDED PRECEDING frame bound
*/
class Frame_unbounded_preceding : public Frame_cursor
{
public:
Frame_unbounded_preceding(THD *thd,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list)
{}
void init(READ_RECORD *info) override {}
void next_partition(ha_rows rownum) override
{
/*
UNBOUNDED PRECEDING frame end just stays on the first row of the
partition. We are top of the frame, so we don't need to update the sum
function.
*/
curr_rownum= rownum;
}
void next_row() override
{
/* Do nothing, UNBOUNDED PRECEDING frame end doesn't move. */
}
ha_rows get_curr_rownum() const override
{
return curr_rownum;
}
private:
ha_rows curr_rownum;
};
/*
UNBOUNDED FOLLOWING frame bound
*/
class Frame_unbounded_following : public Frame_cursor
{
protected:
Partition_read_cursor cursor;
public:
Frame_unbounded_following(THD *thd,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list) :
cursor(thd, partition_list) {}
void init(READ_RECORD *info) override
{
cursor.init(info);
}
void pre_next_partition(ha_rows rownum) override
{
cursor.on_next_partition(rownum);
}
void next_partition(ha_rows rownum) override
{
/* Activate the first row */
cursor.fetch();
add_value_to_items();
/* Walk to the end of the partition, updating the SUM function */
while (!cursor.next())
{
add_value_to_items();
}
}
void next_row() override
{
/* Do nothing, UNBOUNDED FOLLOWING frame end doesn't move */
}
ha_rows get_curr_rownum() const override
{
return cursor.get_rownum();
}
};
class Frame_unbounded_following_set_count : public Frame_unbounded_following
{
public:
Frame_unbounded_following_set_count(
THD *thd,
SQL_I_List<ORDER> *partition_list, SQL_I_List<ORDER> *order_list) :
Frame_unbounded_following(thd, partition_list, order_list) {}
void next_partition(ha_rows rownum) override
{
ha_rows num_rows_in_partition= 0;
if (cursor.fetch())
return;
num_rows_in_partition++;
/* Walk to the end of the partition, find how many rows there are. */
while (!cursor.next())
num_rows_in_partition++;
set_win_funcs_row_count(num_rows_in_partition);
}
ha_rows get_curr_rownum() const override
{
return cursor.get_rownum();
}
protected:
void set_win_funcs_row_count(ha_rows num_rows_in_partition)
{
List_iterator_fast<Item_sum> it(sum_functions);
Item_sum* item;
while ((item= it++))
item->set_partition_row_count(num_rows_in_partition);
}
};
class Frame_unbounded_following_set_count_no_nulls:
public Frame_unbounded_following_set_count
{
public:
Frame_unbounded_following_set_count_no_nulls(THD *thd,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list) :
Frame_unbounded_following_set_count(thd,partition_list, order_list)
{
order_item= order_list->first->item[0];
}
void next_partition(ha_rows rownum) override
{
ha_rows num_rows_in_partition= 0;
if (cursor.fetch())
return;
/* Walk to the end of the partition, find how many rows there are. */
do
{
if (!order_item->is_null())
num_rows_in_partition++;
} while (!cursor.next());
set_win_funcs_row_count(num_rows_in_partition);
}
ha_rows get_curr_rownum() const override
{
return cursor.get_rownum();
}
private:
Item* order_item;
};
2016-03-11 23:29:52 +03:00
/////////////////////////////////////////////////////////////////////////////
// ROWS-type frame bounds
/////////////////////////////////////////////////////////////////////////////
/*
ROWS $n PRECEDING frame bound
*/
class Frame_n_rows_preceding : public Frame_cursor
{
/* Whether this is top of the frame or bottom */
const bool is_top_bound;
const ha_rows n_rows;
/* Number of rows that we need to skip before our cursor starts moving */
ha_rows n_rows_behind;
Table_read_cursor cursor;
public:
Frame_n_rows_preceding(bool is_top_bound_arg, ha_rows n_rows_arg) :
is_top_bound(is_top_bound_arg), n_rows(n_rows_arg), n_rows_behind(0)
{}
void init(READ_RECORD *info) override
{
cursor.init(info);
}
void next_partition(ha_rows rownum) override
{
/*
Position our cursor to point at the first row in the new partition
(for rownum=0, it is already there, otherwise, it lags behind)
*/
cursor.move_to(rownum);
/* Cursor is in the same spot as current row. */
n_rows_behind= 0;
/*
Suppose the bound is ROWS 2 PRECEDING, and current row is row#n:
...
n-3
n-2 --- bound row
n-1
n --- current_row
...
The bound should point at row #(n-2). Bounds are inclusive, so
- bottom bound should add row #(n-2) into the window function
- top bound should remove row (#n-3) from the window function.
*/
move_cursor_if_possible();
}
void next_row() override
{
n_rows_behind++;
move_cursor_if_possible();
}
bool is_outside_computation_bounds() const override
{
/* As a bottom boundary, rows have not yet been added. */
if (!is_top_bound && n_rows - n_rows_behind)
return true;
return false;
}
ha_rows get_curr_rownum() const override
{
return cursor.get_rownum();
}
private:
void move_cursor_if_possible()
{
longlong rows_difference= n_rows - n_rows_behind;
if (rows_difference > 0) /* We still have to wait. */
return;
/* The cursor points to the first row in the frame. */
if (rows_difference == 0)
{
if (!is_top_bound)
{
cursor.fetch();
add_value_to_items();
}
/* For top bound we don't have to remove anything as nothing was added. */
return;
}
/* We need to catch up by one row. */
DBUG_ASSERT(rows_difference == -1);
if (is_top_bound)
{
cursor.fetch();
remove_value_from_items();
cursor.next();
}
else
{
cursor.next();
cursor.fetch();
add_value_to_items();
}
/* We've advanced one row. We are no longer behind. */
n_rows_behind--;
}
};
/*
ROWS ... CURRENT ROW, Bottom bound.
This case is moved to separate class because here we don't need to maintain
our own cursor, or check for partition bound.
*/
class Frame_rows_current_row_bottom : public Frame_cursor
{
public:
Frame_rows_current_row_bottom() : curr_rownum(0) {}
void pre_next_partition(ha_rows rownum) override
{
add_value_to_items();
curr_rownum= rownum;
}
void next_partition(ha_rows rownum) override {}
void pre_next_row() override
{
/* Temp table's current row is current_row. Add it to the window func */
add_value_to_items();
}
void next_row() override
{
curr_rownum++;
};
ha_rows get_curr_rownum() const override
{
return curr_rownum;
}
private:
ha_rows curr_rownum;
};
/*
ROWS-type CURRENT ROW, top bound.
This serves for processing "ROWS BETWEEN CURRENT ROW AND ..." frames.
n-1
n --+ --- current_row, and top frame bound
n+1 |
... |
when the current_row moves to row #n, this frame bound should remove the
row #(n-1) from the window function.
In other words, we need what "ROWS PRECEDING 0" provides.
*/
class Frame_rows_current_row_top: public Frame_n_rows_preceding
{
public:
Frame_rows_current_row_top() :
Frame_n_rows_preceding(true /*top*/, 0 /* n_rows */)
{}
};
/*
ROWS $n FOLLOWING frame bound.
*/
class Frame_n_rows_following : public Frame_cursor
{
/* Whether this is top of the frame or bottom */
const bool is_top_bound;
const ha_rows n_rows;
Partition_read_cursor cursor;
bool at_partition_end;
public:
Frame_n_rows_following(THD *thd,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list,
bool is_top_bound_arg, ha_rows n_rows_arg) :
is_top_bound(is_top_bound_arg), n_rows(n_rows_arg),
cursor(thd, partition_list)
{
}
void init(READ_RECORD *info) override
{
cursor.init(info);
at_partition_end= false;
}
void pre_next_partition(ha_rows rownum) override
{
at_partition_end= false;
cursor.on_next_partition(rownum);
}
/* Move our cursor to be n_rows ahead. */
void next_partition(ha_rows rownum) override
{
if (is_top_bound)
next_part_top(rownum);
else
next_part_bottom(rownum);
}
void next_row() override
{
if (is_top_bound)
next_row_top();
else
next_row_bottom();
}
bool is_outside_computation_bounds() const override
{
/*
The top bound can go over the current partition. In this case,
the sum function has 0 values added to it.
*/
if (at_partition_end && is_top_bound)
return true;
return false;
}
ha_rows get_curr_rownum() const override
{
return cursor.get_rownum();
}
private:
void next_part_top(ha_rows rownum)
{
for (ha_rows i= 0; i < n_rows; i++)
{
if (cursor.fetch())
break;
remove_value_from_items();
if (cursor.next())
at_partition_end= true;
}
}
void next_part_bottom(ha_rows rownum)
{
if (cursor.fetch())
return;
add_value_to_items();
for (ha_rows i= 0; i < n_rows; i++)
{
if (cursor.next())
{
at_partition_end= true;
break;
}
add_value_to_items();
}
return;
}
void next_row_top()
{
if (cursor.fetch()) // PART END OR FAILURE
{
at_partition_end= true;
return;
}
remove_value_from_items();
if (cursor.next())
{
at_partition_end= true;
return;
}
}
void next_row_bottom()
{
if (at_partition_end)
return;
if (cursor.next())
{
at_partition_end= true;
return;
}
add_value_to_items();
}
};
/*
A cursor that performs a table scan between two indices. The indices
are provided by the two cursors representing the top and bottom bound
of the window function's frame definition.
Each scan clears the sum function.
NOTE:
The cursor does not alter the top and bottom cursors.
This type of cursor is expensive computational wise. This is only to be
used when the sum functions do not support removal.
*/
class Frame_scan_cursor : public Frame_cursor
{
public:
Frame_scan_cursor(const Frame_cursor &top_bound,
const Frame_cursor &bottom_bound) :
top_bound(top_bound), bottom_bound(bottom_bound) {}
void init(READ_RECORD *info) override
{
cursor.init(info);
}
void pre_next_partition(ha_rows rownum) override
{
/* TODO(cvicentiu) Sum functions get cleared on next partition anyway during
the window function computation algorithm. Either perform this only in
cursors, or remove it from pre_next_partition.
*/
curr_rownum= rownum;
clear_sum_functions();
}
void next_partition(ha_rows rownum) override
{
compute_values_for_current_row();
}
void pre_next_row() override
{
clear_sum_functions();
}
void next_row() override
{
curr_rownum++;
compute_values_for_current_row();
}
ha_rows get_curr_rownum() const override
{
return curr_rownum;
}
private:
const Frame_cursor &top_bound;
const Frame_cursor &bottom_bound;
Table_read_cursor cursor;
ha_rows curr_rownum;
/* Scan the rows between the top bound and bottom bound. Add all the values
between them, top bound row and bottom bound row inclusive. */
void compute_values_for_current_row()
{
if (top_bound.is_outside_computation_bounds() ||
bottom_bound.is_outside_computation_bounds())
return;
ha_rows start_rownum= top_bound.get_curr_rownum();
ha_rows bottom_rownum= bottom_bound.get_curr_rownum();
DBUG_PRINT("info", ("COMPUTING (%llu %llu)", start_rownum, bottom_rownum));
cursor.move_to(start_rownum);
for (ha_rows idx= start_rownum; idx <= bottom_rownum; idx++)
{
if (cursor.fetch()) //EOF
break;
add_value_to_items();
if (cursor.next()) // EOF
break;
}
}
};
/* A cursor that follows a target cursor. Each time a new row is added,
the window functions are cleared and only have the row at which the target
is point at added to them.
The window functions are cleared if the bounds or the position cursors are
outside computational bounds.
*/
class Frame_positional_cursor : public Frame_cursor
{
public:
Frame_positional_cursor(const Frame_cursor &position_cursor) :
position_cursor(position_cursor), top_bound(NULL),
bottom_bound(NULL), offset(NULL), overflowed(false),
negative_offset(false) {}
Frame_positional_cursor(const Frame_cursor &position_cursor,
const Frame_cursor &top_bound,
const Frame_cursor &bottom_bound,
Item &offset,
bool negative_offset) :
position_cursor(position_cursor), top_bound(&top_bound),
bottom_bound(&bottom_bound), offset(&offset),
negative_offset(negative_offset) {}
void init(READ_RECORD *info) override
{
cursor.init(info);
}
void pre_next_partition(ha_rows rownum) override
{
/* The offset is dependant on the current row values. We can only get
* it here accurately. When fetching other rows, it changes. */
save_offset_value();
}
void next_partition(ha_rows rownum) override
{
save_positional_value();
}
void pre_next_row() override
{
/* The offset is dependant on the current row values. We can only get
* it here accurately. When fetching other rows, it changes. */
save_offset_value();
}
void next_row() override
{
save_positional_value();
}
ha_rows get_curr_rownum() const override
{
return position_cursor.get_curr_rownum();
}
private:
/* Check if a our position is within bounds.
* The position is passed as a parameter to avoid recalculating it. */
bool position_is_within_bounds()
{
if (!offset)
return !position_cursor.is_outside_computation_bounds();
if (overflowed)
return false;
/* No valid bound to compare to. */
if (position_cursor.is_outside_computation_bounds() ||
top_bound->is_outside_computation_bounds() ||
bottom_bound->is_outside_computation_bounds())
return false;
/* We are over the bound. */
if (position < top_bound->get_curr_rownum())
return false;
if (position > bottom_bound->get_curr_rownum())
return false;
return true;
}
/* Get the current position, accounting for the offset value, if present.
NOTE: This function does not check over/underflow.
*/
void get_current_position()
{
position = position_cursor.get_curr_rownum();
overflowed= false;
if (offset)
{
if (offset_value < 0 &&
position + offset_value > position)
{
overflowed= true;
}
if (offset_value > 0 &&
position + offset_value < position)
{
overflowed= true;
}
position += offset_value;
}
}
void save_offset_value()
{
if (offset)
offset_value= offset->val_int() * (negative_offset ? -1 : 1);
else
offset_value= 0;
}
void save_positional_value()
{
get_current_position();
if (!position_is_within_bounds())
clear_sum_functions();
else
{
cursor.move_to(position);
cursor.fetch();
add_value_to_items();
}
}
const Frame_cursor &position_cursor;
const Frame_cursor *top_bound;
const Frame_cursor *bottom_bound;
Item *offset;
Table_read_cursor cursor;
ha_rows position;
longlong offset_value;
bool overflowed;
bool negative_offset;
};
2016-03-11 23:29:52 +03:00
/*
Get a Frame_cursor for a frame bound. This is a "factory function".
*/
Frame_cursor *get_frame_cursor(THD *thd, Window_spec *spec, bool is_top_bound)
{
Window_frame *frame= spec->window_frame;
if (!frame)
{
/*
The docs say this about the lack of frame clause:
Let WD be a window structure descriptor.
...
If WD has no window framing clause, then
Case:
i) If the window ordering clause of WD is not present, then WF is the
window partition of R.
ii) Otherwise, WF consists of all rows of the partition of R that
precede R or are peers of R in the window ordering of the window
partition defined by the window ordering clause.
For case #ii, the frame bounds essentially are "RANGE BETWEEN UNBOUNDED
PRECEDING AND CURRENT ROW".
For the case #i, without ordering clause all rows are considered peers,
so again the same frame bounds can be used.
*/
if (is_top_bound)
return new Frame_unbounded_preceding(thd,
spec->partition_list,
spec->order_list);
else
return new Frame_range_current_row_bottom(thd,
spec->partition_list,
spec->order_list);
}
Window_frame_bound *bound= is_top_bound? frame->top_bound :
frame->bottom_bound;
if (bound->precedence_type == Window_frame_bound::PRECEDING ||
bound->precedence_type == Window_frame_bound::FOLLOWING)
{
bool is_preceding= (bound->precedence_type ==
Window_frame_bound::PRECEDING);
if (bound->offset == NULL) /* this is UNBOUNDED */
{
/* The following serve both RANGE and ROWS: */
if (is_preceding)
return new Frame_unbounded_preceding(thd,
spec->partition_list,
spec->order_list);
return new Frame_unbounded_following(thd,
spec->partition_list,
spec->order_list);
}
if (frame->units == Window_frame::UNITS_ROWS)
{
ha_rows n_rows= bound->offset->val_int();
/* These should be handled in the parser */
DBUG_ASSERT(!bound->offset->null_value);
DBUG_ASSERT((longlong) n_rows >= 0);
if (is_preceding)
return new Frame_n_rows_preceding(is_top_bound, n_rows);
return new Frame_n_rows_following(
thd, spec->partition_list, spec->order_list,
is_top_bound, n_rows);
}
else
{
if (is_top_bound)
return new Frame_range_n_top(
thd, spec->partition_list, spec->order_list,
is_preceding, bound->offset);
return new Frame_range_n_bottom(thd,
spec->partition_list, spec->order_list,
is_preceding, bound->offset);
}
}
if (bound->precedence_type == Window_frame_bound::CURRENT)
{
if (frame->units == Window_frame::UNITS_ROWS)
{
if (is_top_bound)
return new Frame_rows_current_row_top;
return new Frame_rows_current_row_bottom;
}
else
{
if (is_top_bound)
return new Frame_range_current_row_top(
thd, spec->partition_list, spec->order_list);
return new Frame_range_current_row_bottom(
thd, spec->partition_list, spec->order_list);
}
}
return NULL;
}
static
bool add_special_frame_cursors(THD *thd, Cursor_manager *cursor_manager,
Item_window_func *window_func)
{
Window_spec *spec= window_func->window_spec;
Item_sum *item_sum= window_func->window_func();
DBUG_PRINT("info", ("Get arg count: %d", item_sum->get_arg_count()));
Frame_cursor *fc;
switch (item_sum->sum_func())
{
case Item_sum::CUME_DIST_FUNC:
fc= new Frame_unbounded_preceding(thd,
spec->partition_list,
spec->order_list);
fc->add_sum_func(item_sum);
cursor_manager->add_cursor(fc);
fc= new Frame_range_current_row_bottom(thd,
spec->partition_list,
spec->order_list);
fc->add_sum_func(item_sum);
cursor_manager->add_cursor(fc);
break;
case Item_sum::LEAD_FUNC:
case Item_sum::LAG_FUNC:
{
Frame_cursor *bottom_bound= new Frame_unbounded_following(thd,
spec->partition_list,
spec->order_list);
Frame_cursor *top_bound= new Frame_unbounded_preceding(thd,
spec->partition_list,
spec->order_list);
Frame_cursor *current_row_pos= new Frame_rows_current_row_bottom;
cursor_manager->add_cursor(bottom_bound);
cursor_manager->add_cursor(top_bound);
cursor_manager->add_cursor(current_row_pos);
DBUG_ASSERT(item_sum->fixed());
bool negative_offset= item_sum->sum_func() == Item_sum::LAG_FUNC;
fc= new Frame_positional_cursor(*current_row_pos,
*top_bound, *bottom_bound,
*item_sum->get_arg(1),
negative_offset);
fc->add_sum_func(item_sum);
cursor_manager->add_cursor(fc);
break;
}
case Item_sum::FIRST_VALUE_FUNC:
{
Frame_cursor *bottom_bound= get_frame_cursor(thd, spec, false);
Frame_cursor *top_bound= get_frame_cursor(thd, spec, true);
cursor_manager->add_cursor(bottom_bound);
cursor_manager->add_cursor(top_bound);
DBUG_ASSERT(item_sum->fixed());
Item *offset_item= new (thd->mem_root) Item_int(thd, 0);
offset_item->fix_fields(thd, &offset_item);
fc= new Frame_positional_cursor(*top_bound,
*top_bound, *bottom_bound,
*offset_item, false);
fc->add_sum_func(item_sum);
cursor_manager->add_cursor(fc);
break;
}
case Item_sum::LAST_VALUE_FUNC:
{
Frame_cursor *bottom_bound= get_frame_cursor(thd, spec, false);
Frame_cursor *top_bound= get_frame_cursor(thd, spec, true);
cursor_manager->add_cursor(bottom_bound);
cursor_manager->add_cursor(top_bound);
DBUG_ASSERT(item_sum->fixed());
Item *offset_item= new (thd->mem_root) Item_int(thd, 0);
offset_item->fix_fields(thd, &offset_item);
fc= new Frame_positional_cursor(*bottom_bound,
*top_bound, *bottom_bound,
*offset_item, false);
fc->add_sum_func(item_sum);
cursor_manager->add_cursor(fc);
break;
}
case Item_sum::NTH_VALUE_FUNC:
{
Frame_cursor *bottom_bound= get_frame_cursor(thd, spec, false);
Frame_cursor *top_bound= get_frame_cursor(thd, spec, true);
cursor_manager->add_cursor(bottom_bound);
cursor_manager->add_cursor(top_bound);
DBUG_ASSERT(item_sum->fixed());
Item *int_item= new (thd->mem_root) Item_int(thd, 1);
Item *offset_func= new (thd->mem_root)
Item_func_minus(thd, item_sum->get_arg(1),
int_item);
if (offset_func->fix_fields(thd, &offset_func))
return true;
fc= new Frame_positional_cursor(*top_bound,
*top_bound, *bottom_bound,
*offset_func, false);
fc->add_sum_func(item_sum);
cursor_manager->add_cursor(fc);
break;
}
case Item_sum::PERCENTILE_CONT_FUNC:
case Item_sum::PERCENTILE_DISC_FUNC:
{
fc= new Frame_unbounded_preceding(thd,
spec->partition_list,
spec->order_list);
fc->add_sum_func(item_sum);
cursor_manager->add_cursor(fc);
fc= new Frame_unbounded_following(thd,
spec->partition_list,
spec->order_list);
fc->add_sum_func(item_sum);
cursor_manager->add_cursor(fc);
break;
}
default:
fc= new Frame_unbounded_preceding(
thd, spec->partition_list, spec->order_list);
fc->add_sum_func(item_sum);
cursor_manager->add_cursor(fc);
fc= new Frame_rows_current_row_bottom;
fc->add_sum_func(item_sum);
cursor_manager->add_cursor(fc);
}
return false;
}
static bool is_computed_with_remove(Item_sum::Sumfunctype sum_func)
{
switch (sum_func)
{
case Item_sum::CUME_DIST_FUNC:
case Item_sum::ROW_NUMBER_FUNC:
case Item_sum::RANK_FUNC:
case Item_sum::DENSE_RANK_FUNC:
case Item_sum::NTILE_FUNC:
case Item_sum::FIRST_VALUE_FUNC:
case Item_sum::LAST_VALUE_FUNC:
case Item_sum::PERCENTILE_CONT_FUNC:
case Item_sum::PERCENTILE_DISC_FUNC:
return false;
default:
return true;
}
}
/*
Create required frame cursors for the list of window functions.
Register all functions to their appropriate cursors.
If the window functions share the same frame specification,
those window functions will be registered to the same cursor.
*/
bool get_window_functions_required_cursors(
THD *thd,
List<Item_window_func>& window_functions,
List<Cursor_manager> *cursor_managers)
{
List_iterator_fast<Item_window_func> it(window_functions);
Item_window_func* item_win_func;
Item_sum *sum_func;
while ((item_win_func= it++))
{
Cursor_manager *cursor_manager = new Cursor_manager();
sum_func = item_win_func->window_func();
Frame_cursor *fc;
/*
Some window functions require the partition size for computing values.
Add a cursor that retrieves it as the first one in the list if necessary.
*/
if (item_win_func->requires_partition_size())
{
if (item_win_func->only_single_element_order_list())
{
fc= new Frame_unbounded_following_set_count_no_nulls(thd,
item_win_func->window_spec->partition_list,
item_win_func->window_spec->order_list);
}
else
{
fc= new Frame_unbounded_following_set_count(thd,
item_win_func->window_spec->partition_list,
item_win_func->window_spec->order_list);
}
fc->add_sum_func(sum_func);
cursor_manager->add_cursor(fc);
}
/*
If it is not a regular window function that follows frame specifications,
and/or specific cursors are required. ROW_NUM, RANK, NTILE and others
follow such rules. Check is_frame_prohibited check for the full list.
TODO(cvicentiu) This approach is messy. Every time a function allows
computation in a certain way, we have to add an extra method to this
factory function. It is better to have window functions output
their own cursors, as needed. This way, the logic is bound
only to the implementation of said window function. Regular aggregate
functions can keep the default frame generating code, overwrite it or
add to it.
*/
if (item_win_func->is_frame_prohibited() ||
item_win_func->requires_special_cursors())
{
if (add_special_frame_cursors(thd, cursor_manager, item_win_func))
{
delete cursor_manager;
return true;
}
cursor_managers->push_back(cursor_manager);
continue;
}
Frame_cursor *frame_bottom= get_frame_cursor(thd,
item_win_func->window_spec, false);
Frame_cursor *frame_top= get_frame_cursor(thd,
item_win_func->window_spec, true);
frame_bottom->add_sum_func(sum_func);
frame_top->add_sum_func(sum_func);
/*
The order of these cursors is important. A sum function
must first add values (via frame_bottom) then remove them via
frame_top. Removing items first doesn't make sense in the case of all
window functions.
*/
cursor_manager->add_cursor(frame_bottom);
cursor_manager->add_cursor(frame_top);
if (is_computed_with_remove(sum_func->sum_func()) &&
!sum_func->supports_removal())
{
frame_bottom->set_no_action();
frame_top->set_no_action();
Frame_cursor *scan_cursor= new Frame_scan_cursor(*frame_top,
*frame_bottom);
scan_cursor->add_sum_func(sum_func);
cursor_manager->add_cursor(scan_cursor);
}
cursor_managers->push_back(cursor_manager);
}
return false;
}
/**
Helper function that takes a list of window functions and writes
their values in the current table record.
*/
static
bool save_window_function_values(List<Item_window_func>& window_functions,
TABLE *tbl, uchar *rowid_buf)
{
List_iterator_fast<Item_window_func> iter(window_functions);
JOIN_TAB *join_tab= tbl->reginfo.join_tab;
tbl->file->ha_rnd_pos(tbl->record[0], rowid_buf);
store_record(tbl, record[1]);
while (Item_window_func *item_win= iter++)
item_win->save_in_field(item_win->result_field, true);
/*
In case we have window functions present, an extra step is required
to compute all the fields from the temporary table.
In case we have a compound expression such as: expr + expr,
where one of the terms has a window function inside it, only
after computing window function values we actually know the true
final result of the compounded expression.
Go through all the func items and save their values once again in the
corresponding temp table fields. Do this for each row in the table.
This needs to be done earlier because ORDER BY clause can also have
a window function, so we need to make sure all the fields of the temp.table
are updated before we do the filesort. So is best to update the other fields
that contain the window functions along with the computation of window
functions.
*/
Item **func_ptr= join_tab->tmp_table_param->items_to_copy;
Item *func;
for (; (func = *func_ptr) ; func_ptr++)
{
if (func->with_window_func() && func->type() != Item::WINDOW_FUNC_ITEM)
func->save_in_result_field(true);
}
int err= tbl->file->ha_update_row(tbl->record[1], tbl->record[0]);
if (err && err != HA_ERR_RECORD_IS_THE_SAME)
return true;
return false;
}
/*
TODO(cvicentiu) update this comment to reflect the new execution.
Streamed window function computation with window frames.
We make a single pass over the ordered temp.table, but we're using three
cursors:
- current row - the row that we're computing window func value for)
- start_bound - the start of the frame
- bottom_bound - the end of the frame
All three cursors move together.
@todo
Provided bounds have their 'cursors'... is it better to re-clone their
cursors or re-position them onto the current row?
@detail
ROWS BETWEEN 3 PRECEDING -- frame start
AND 3 FOLLOWING -- frame end
/------ frame end (aka BOTTOM)
Dataset start |
--------====*=======[*]========*========-------->> dataset end
| \
| +-------- current row
|
\-------- frame start ("TOP")
- frame_end moves forward and adds rows into the aggregate function.
- frame_start follows behind and removes rows from the aggregate function.
- current_row is the row where the value of aggregate function is stored.
@TODO: Only the first cursor needs to check for run-out-of-partition
condition (Others can catch up by counting rows?)
*/
bool compute_window_func(THD *thd,
List<Item_window_func>& window_functions,
List<Cursor_manager>& cursor_managers,
TABLE *tbl,
SORT_INFO *filesort_result)
{
List_iterator_fast<Item_window_func> iter_win_funcs(window_functions);
List_iterator_fast<Cursor_manager> iter_cursor_managers(cursor_managers);
bool ret= false;
uint err;
READ_RECORD info;
if (init_read_record(&info, current_thd, tbl, NULL/*select*/, filesort_result,
0, 1, FALSE))
return true;
Cursor_manager *cursor_manager;
while ((cursor_manager= iter_cursor_managers++))
cursor_manager->initialize_cursors(&info);
/* One partition tracker for each window function. */
List<Group_bound_tracker> partition_trackers;
Item_window_func *win_func;
while ((win_func= iter_win_funcs++))
{
Group_bound_tracker *tracker= new Group_bound_tracker(thd,
win_func->window_spec->partition_list);
// TODO(cvicentiu) This should be removed and placed in constructor.
tracker->init();
partition_trackers.push_back(tracker);
}
List_iterator_fast<Group_bound_tracker> iter_part_trackers(partition_trackers);
ha_rows rownum= 0;
uchar *rowid_buf= (uchar*) my_malloc(PSI_INSTRUMENT_ME, tbl->file->ref_length, MYF(0));
while (true)
{
if ((err= info.read_record()))
break; // End of file.
/* Remember current row so that we can restore it before computing
each window function. */
tbl->file->position(tbl->record[0]);
memcpy(rowid_buf, tbl->file->ref, tbl->file->ref_length);
iter_win_funcs.rewind();
iter_part_trackers.rewind();
iter_cursor_managers.rewind();
Group_bound_tracker *tracker;
while ((win_func= iter_win_funcs++) &&
(tracker= iter_part_trackers++) &&
(cursor_manager= iter_cursor_managers++))
{
if (tracker->check_if_next_group() || (rownum == 0))
{
/* TODO(cvicentiu)
Clearing window functions should happen through cursors. */
win_func->window_func()->clear();
cursor_manager->notify_cursors_partition_changed(rownum);
}
else
{
cursor_manager->notify_cursors_next_row();
}
/* Check if we found any error in the window function while adding values
through cursors. */
if (unlikely(thd->is_error() || thd->is_killed()))
{
ret= true;
break;
}
/* Return to current row after notifying cursors for each window
function. */
MDEV-9101 Limit size of created disk temporary files and tables Two new variables added: - max_tmp_space_usage : Limits the the temporary space allowance per user - max_total_tmp_space_usage: Limits the temporary space allowance for all users. New status variables: tmp_space_used & max_tmp_space_used New field in information_schema.process_list: TMP_SPACE_USED The temporary space is counted for: - All SQL level temporary files. This includes files for filesort, transaction temporary space, analyze, binlog_stmt_cache etc. It does not include engine internal temporary files used for repair, alter table, index pre sorting etc. - All internal on disk temporary tables created as part of resolving a SELECT, multi-source update etc. Special cases: - When doing a commit, the last flush of the binlog_stmt_cache will not cause an error even if the temporary space limit is exceeded. This is to avoid giving errors on commit. This means that a user can temporary go over the limit with up to binlog_stmt_cache_size. Noteworthy issue: - One has to be careful when using small values for max_tmp_space_limit together with binary logging and with non transactional tables. If a the binary log entry for the query is bigger than binlog_stmt_cache_size and one hits the limit of max_tmp_space_limit when flushing the entry to disk, the query will abort and the binary log will not contain the last changes to the table. This will also stop the slave! This is also true for all Aria tables as Aria cannot do rollback (except in case of crashes)! One way to avoid it is to use @@binlog_format=statement for queries that updates a lot of rows. Implementation: - All writes to temporary files or internal temporary tables, that increases the file size, are routed through temp_file_size_cb_func() which updates and checks the temp space usage. - Most of the temporary file monitoring is done inside IO_CACHE. Temporary file monitoring is done inside the Aria engine. - MY_TRACK and MY_TRACK_WITH_LIMIT are new flags for ini_io_cache(). MY_TRACK means that we track the file usage. TRACK_WITH_LIMIT means that we track the file usage and we give an error if the limit is breached. This is used to not give an error on commit when binlog_stmp_cache is flushed. - global_tmp_space_used contains the total tmp space used so far. This is needed quickly check against max_total_tmp_space_usage. - Temporary space errors are using EE_LOCAL_TMP_SPACE_FULL and handler errors are using HA_ERR_LOCAL_TMP_SPACE_FULL. This is needed until we move general errors to it's own error space so that they cannot conflict with system error numbers. - Return value of my_chsize() and mysql_file_chsize() has changed so that -1 is returned in the case my_chsize() could not decrease the file size (very unlikely and will not happen on modern systems). All calls to _chsize() are updated to check for > 0 as the error condition. - At the destruction of THD we check that THD::tmp_file_space == 0 - At server end we check that global_tmp_space_used == 0 - As a precaution against errors in the tmp_space_used code, one can set max_tmp_space_usage and max_total_tmp_space_usage to 0 to disable the tmp space quota errors. - truncate_io_cache() function added. - Aria tables using static or dynamic row length are registered in 8K increments to avoid some calls to update_tmp_file_size(). Other things: - Ensure that all handler errors are registered. Before, some engine errors could be printed as "Unknown error". - Fixed bug in filesort() that causes a assert if there was an error when writing to the temporay file. - Fixed that compute_window_func() now takes into account write errors. - In case of parallel replication, rpl_group_info::cleanup_context() could call trans_rollback() with thd->error set, which would cause an assert. Fixed by resetting the error before calling trans_rollback(). - Fixed bug in subselect3.inc which caused following test to use heap tables with low value for max_heap_table_size - Fixed bug in sql_expression_cache where it did not overflow heap table to Aria table. - Added Max_tmp_disk_space_used to slow query log. - Fixed some bugs in log_slow_innodb.test
2024-03-14 17:59:00 +01:00
if (tbl->file->ha_rnd_pos(tbl->record[0], rowid_buf))
{
ret= true;
break;
}
}
/* We now have computed values for each window function. They can now
be saved in the current row. */
MDEV-9101 Limit size of created disk temporary files and tables Two new variables added: - max_tmp_space_usage : Limits the the temporary space allowance per user - max_total_tmp_space_usage: Limits the temporary space allowance for all users. New status variables: tmp_space_used & max_tmp_space_used New field in information_schema.process_list: TMP_SPACE_USED The temporary space is counted for: - All SQL level temporary files. This includes files for filesort, transaction temporary space, analyze, binlog_stmt_cache etc. It does not include engine internal temporary files used for repair, alter table, index pre sorting etc. - All internal on disk temporary tables created as part of resolving a SELECT, multi-source update etc. Special cases: - When doing a commit, the last flush of the binlog_stmt_cache will not cause an error even if the temporary space limit is exceeded. This is to avoid giving errors on commit. This means that a user can temporary go over the limit with up to binlog_stmt_cache_size. Noteworthy issue: - One has to be careful when using small values for max_tmp_space_limit together with binary logging and with non transactional tables. If a the binary log entry for the query is bigger than binlog_stmt_cache_size and one hits the limit of max_tmp_space_limit when flushing the entry to disk, the query will abort and the binary log will not contain the last changes to the table. This will also stop the slave! This is also true for all Aria tables as Aria cannot do rollback (except in case of crashes)! One way to avoid it is to use @@binlog_format=statement for queries that updates a lot of rows. Implementation: - All writes to temporary files or internal temporary tables, that increases the file size, are routed through temp_file_size_cb_func() which updates and checks the temp space usage. - Most of the temporary file monitoring is done inside IO_CACHE. Temporary file monitoring is done inside the Aria engine. - MY_TRACK and MY_TRACK_WITH_LIMIT are new flags for ini_io_cache(). MY_TRACK means that we track the file usage. TRACK_WITH_LIMIT means that we track the file usage and we give an error if the limit is breached. This is used to not give an error on commit when binlog_stmp_cache is flushed. - global_tmp_space_used contains the total tmp space used so far. This is needed quickly check against max_total_tmp_space_usage. - Temporary space errors are using EE_LOCAL_TMP_SPACE_FULL and handler errors are using HA_ERR_LOCAL_TMP_SPACE_FULL. This is needed until we move general errors to it's own error space so that they cannot conflict with system error numbers. - Return value of my_chsize() and mysql_file_chsize() has changed so that -1 is returned in the case my_chsize() could not decrease the file size (very unlikely and will not happen on modern systems). All calls to _chsize() are updated to check for > 0 as the error condition. - At the destruction of THD we check that THD::tmp_file_space == 0 - At server end we check that global_tmp_space_used == 0 - As a precaution against errors in the tmp_space_used code, one can set max_tmp_space_usage and max_total_tmp_space_usage to 0 to disable the tmp space quota errors. - truncate_io_cache() function added. - Aria tables using static or dynamic row length are registered in 8K increments to avoid some calls to update_tmp_file_size(). Other things: - Ensure that all handler errors are registered. Before, some engine errors could be printed as "Unknown error". - Fixed bug in filesort() that causes a assert if there was an error when writing to the temporay file. - Fixed that compute_window_func() now takes into account write errors. - In case of parallel replication, rpl_group_info::cleanup_context() could call trans_rollback() with thd->error set, which would cause an assert. Fixed by resetting the error before calling trans_rollback(). - Fixed bug in subselect3.inc which caused following test to use heap tables with low value for max_heap_table_size - Fixed bug in sql_expression_cache where it did not overflow heap table to Aria table. - Added Max_tmp_disk_space_used to slow query log. - Fixed some bugs in log_slow_innodb.test
2024-03-14 17:59:00 +01:00
if (save_window_function_values(window_functions, tbl, rowid_buf))
{
ret= true;
break;
}
rownum++;
}
my_free(rowid_buf);
partition_trackers.delete_elements();
end_read_record(&info);
return ret;
}
/* Make a list that is a concation of two lists of ORDER elements */
static ORDER* concat_order_lists(MEM_ROOT *mem_root, ORDER *list1, ORDER *list2)
{
if (!list1)
{
list1= list2;
list2= NULL;
}
ORDER *res= NULL; // first element in the new list
ORDER *prev= NULL; // last element in the new list
ORDER *cur_list= list1; // this goes through list1, list2
while (cur_list)
{
for (ORDER *cur= cur_list; cur; cur= cur->next)
{
ORDER *copy= (ORDER*)alloc_root(mem_root, sizeof(ORDER));
memcpy(copy, cur, sizeof(ORDER));
if (prev)
prev->next= copy;
prev= copy;
if (!res)
res= copy;
}
cur_list= (cur_list == list1)? list2: NULL;
}
if (prev)
prev->next= NULL;
return res;
}
bool Window_func_runner::add_function_to_run(Item_window_func *win_func)
{
Item_sum *sum_func= win_func->window_func();
sum_func->setup_window_func(current_thd, win_func->window_spec);
Item_sum::Sumfunctype type= win_func->window_func()->sum_func();
switch (type)
{
/* Distinct is not yet supported. */
case Item_sum::GROUP_CONCAT_FUNC:
my_error(ER_NOT_SUPPORTED_YET, MYF(0),
"GROUP_CONCAT() aggregate as window function");
return true;
case Item_sum::SUM_DISTINCT_FUNC:
my_error(ER_NOT_SUPPORTED_YET, MYF(0),
"SUM(DISTINCT) aggregate as window function");
return true;
case Item_sum::AVG_DISTINCT_FUNC:
my_error(ER_NOT_SUPPORTED_YET, MYF(0),
"AVG(DISTINCT) aggregate as window function");
return true;
case Item_sum::COUNT_DISTINCT_FUNC:
my_error(ER_NOT_SUPPORTED_YET, MYF(0),
"COUNT(DISTINCT) aggregate as window function");
return true;
case Item_sum::JSON_ARRAYAGG_FUNC:
my_error(ER_NOT_SUPPORTED_YET, MYF(0),
"JSON_ARRAYAGG() aggregate as window function");
return true;
case Item_sum::JSON_OBJECTAGG_FUNC:
my_error(ER_NOT_SUPPORTED_YET, MYF(0),
"JSON_OBJECTAGG() aggregate as window function");
return true;
default:
break;
}
return window_functions.push_back(win_func);
}
/*
Compute the value of window function for all rows.
*/
bool Window_func_runner::exec(THD *thd, TABLE *tbl, SORT_INFO *filesort_result)
{
List_iterator_fast<Item_window_func> it(window_functions);
Item_window_func *win_func;
while ((win_func= it++))
{
win_func->set_phase_to_computation();
// TODO(cvicentiu) Setting the aggregator should probably be done during
// setup of Window_funcs_sort.
win_func->window_func()->set_aggregator(thd,
Aggregator::SIMPLE_AGGREGATOR);
}
it.rewind();
List<Cursor_manager> cursor_managers;
if (get_window_functions_required_cursors(thd, window_functions,
&cursor_managers))
return true;
/* Go through the sorted array and compute the window function */
bool is_error= compute_window_func(thd,
window_functions,
cursor_managers,
tbl, filesort_result);
while ((win_func= it++))
{
win_func->set_phase_to_retrieval();
}
cursor_managers.delete_elements();
return is_error;
}
bool Window_funcs_sort::exec(JOIN *join, bool keep_filesort_result)
{
THD *thd= join->thd;
JOIN_TAB *join_tab= join->join_tab + join->total_join_tab_cnt();
/* Sort the table based on the most specific sorting criteria of
the window functions. */
if (create_sort_index(thd, join, join_tab, filesort))
return true;
TABLE *tbl= join_tab->table;
SORT_INFO *filesort_result= join_tab->filesort_result;
bool is_error= runner.exec(thd, tbl, filesort_result);
if (!keep_filesort_result)
{
delete join_tab->filesort_result;
join_tab->filesort_result= NULL;
}
return is_error;
}
bool Window_funcs_sort::setup(THD *thd, SQL_SELECT *sel,
List_iterator<Item_window_func> &it,
JOIN_TAB *join_tab)
{
Window_spec *spec;
Item_window_func *win_func= it.peek();
Item_window_func *win_func_with_longest_order= NULL;
int longest_order_elements= -1;
/* The iterator should point to a valid function at the start of execution. */
DBUG_ASSERT(win_func);
do
{
spec= win_func->window_spec;
int win_func_order_elements= spec->partition_list->elements +
spec->order_list->elements;
if (win_func_order_elements >= longest_order_elements)
{
win_func_with_longest_order= win_func;
longest_order_elements= win_func_order_elements;
}
if (runner.add_function_to_run(win_func))
return true;
it++;
win_func= it.peek();
} while (win_func && !(win_func->marker & MARKER_SORTORDER_CHANGE));
/*
The sort criteria must be taken from the last win_func in the group of
adjacent win_funcs that do not have MARKER_SORTORDER_CHANGE. This is
because the sort order must be the most specific sorting criteria defined
within the window function group. This ensures that we sort the table
in a way that the result is valid for all window functions belonging to
this Window_funcs_sort.
*/
spec= win_func_with_longest_order->window_spec;
ORDER* sort_order= concat_order_lists(thd->mem_root,
spec->partition_list->first,
spec->order_list->first);
if (sort_order == NULL) // No partition or order by clause.
{
/* TODO(cvicentiu) This is used as a way to allow an empty OVER ()
clause for window functions. However, a better approach is
to not call Filesort at all in this case and just read whatever order
the temporary table has.
Due to cursors not working for out_of_memory cases (yet!), we have to run
filesort to generate a sort buffer of the results.
In this case we sort by the first field of the temporary table.
We should have this field available, even if it is a window_function
field. We don't care of the particular sorting result in this case.
*/
ORDER *order= (ORDER *)alloc_root(thd->mem_root, sizeof(ORDER));
memset(order, 0, sizeof(*order));
Item_field *item=
new (thd->mem_root) Item_field(thd, join_tab->table->field[0]);
if (item)
item->set_refers_to_temp_table();
order->item= (Item **)alloc_root(thd->mem_root, 2 * sizeof(Item *));
order->item[1]= NULL;
order->item[0]= item;
order->field= join_tab->table->field[0];
sort_order= order;
}
filesort= new (thd->mem_root) Filesort(sort_order, HA_POS_ERROR, true, NULL);
/* Apply the same condition that the subsequent sort has. */
filesort->select= sel;
return false;
}
bool Window_funcs_computation::setup(THD *thd,
List<Item_window_func> *window_funcs,
JOIN_TAB *tab)
{
order_window_funcs_by_window_specs(window_funcs);
SQL_SELECT *sel= NULL;
/*
If the tmp table is filtered during sorting
(ex: SELECT with HAVING && ORDER BY), we must make sure to keep the
filtering conditions when we perform sorting for window function
computation.
*/
if (tab->filesort && tab->filesort->select)
{
sel= tab->filesort->select;
DBUG_ASSERT(!sel->quick);
}
Window_funcs_sort *srt;
List_iterator<Item_window_func> iter(*window_funcs);
while (iter.peek())
{
if (!(srt= new Window_funcs_sort()) ||
srt->setup(thd, sel, iter, tab))
{
return true;
}
win_func_sorts.push_back(srt, thd->mem_root);
}
return false;
}
bool Window_funcs_computation::exec(JOIN *join, bool keep_last_filesort_result)
{
List_iterator<Window_funcs_sort> it(win_func_sorts);
Window_funcs_sort *srt;
uint counter= 0; /* Count how many sorts we've executed. */
/* Execute each sort */
while ((srt = it++))
{
counter++;
bool keep_filesort_result= keep_last_filesort_result &&
counter == win_func_sorts.elements;
if (srt->exec(join, keep_filesort_result))
return true;
}
return false;
}
void Window_funcs_computation::cleanup()
{
List_iterator<Window_funcs_sort> it(win_func_sorts);
Window_funcs_sort *srt;
while ((srt = it++))
{
srt->cleanup();
delete srt;
}
}
Explain_aggr_window_funcs*
Window_funcs_computation::save_explain_plan(MEM_ROOT *mem_root,
bool is_analyze)
{
Explain_aggr_window_funcs *xpl= new Explain_aggr_window_funcs;
List_iterator<Window_funcs_sort> it(win_func_sorts);
Window_funcs_sort *srt;
if (!xpl)
return 0;
while ((srt = it++))
{
Explain_aggr_filesort *eaf=
new Explain_aggr_filesort(mem_root, is_analyze, srt->filesort);
if (!eaf)
return 0;
xpl->sorts.push_back(eaf, mem_root);
}
return xpl;
}
bool st_select_lex::add_window_func(Item_window_func *win_func)
{
if (parsing_place != SELECT_LIST)
fields_in_window_functions+= win_func->window_func()->argument_count();
return window_funcs.push_back(win_func);
}
/////////////////////////////////////////////////////////////////////////////
// Unneeded comments (will be removed when we develop a replacement for
// the feature that was attempted here
/////////////////////////////////////////////////////////////////////////////
/*
TODO Get this code to set can_compute_window_function during preparation,
not during execution.
The reason for this is the following:
Our single scan optimization for window functions without tmp table,
is valid, if and only if, we only need to perform one sorting operation,
via filesort. The cases where we need to perform one sorting operation only:
* A select with only one window function.
* A select with multiple window functions, but they must have their
partition and order by clauses compatible. This means that one ordering
is acceptable for both window functions.
For example:
partition by a, b, c; order by d, e results in sorting by a b c d e.
partition by a; order by d results in sorting by a d.
This kind of sorting is compatible. The less specific partition does
not care for the order of b and c columns so it is valid if we sort
by those in case of equality over a.
partition by a, b; order by d, e results in sorting by a b d e
partition by a; order by e results in sorting by a e
This sorting is incompatible due to the order by clause. The partition by
clause is compatible, (partition by a) is a prefix for (partition by a, b)
However, order by e is not a prefix for order by d, e, thus it is not
compatible.
The rule for having compatible sorting is thus:
Each partition order must contain the other window functions partitions
prefixes, or be a prefix itself. This must hold true for all partitions.
Analog for the order by clause.
*/
#if 0
List<Item_window_func> window_functions;
SQL_I_List<ORDER> largest_partition;
SQL_I_List<ORDER> largest_order_by;
bool can_compute_window_live = !need_tmp;
// Construct the window_functions item list and check if they can be
// computed using only one sorting.
//
// TODO: Perhaps group functions into compatible sorting bins
// to minimize the number of sorting passes required to compute all of them.
while ((item= it++))
{
if (item->type() == Item::WINDOW_FUNC_ITEM)
{
Item_window_func *item_win = (Item_window_func *) item;
window_functions.push_back(item_win);
if (!can_compute_window_live)
continue; // No point checking since we have to perform multiple sorts.
Window_spec *spec = item_win->window_spec;
// Having an empty partition list on one window function and a
// not empty list on a separate window function causes the sorting
// to be incompatible.
//
// Example:
// over (partition by a, order by x) && over (order by x).
//
// The first function requires an ordering by a first and then by x,
2025-03-09 18:19:33 +02:00
// while the second function requires an ordering by x first.
// The same restriction is not required for the order by clause.
if (largest_partition.elements && !spec->partition_list.elements)
{
can_compute_window_live= FALSE;
continue;
}
can_compute_window_live= test_if_order_compatible(largest_partition,
spec->partition_list);
if (!can_compute_window_live)
continue;
can_compute_window_live= test_if_order_compatible(largest_order_by,
spec->order_list);
if (!can_compute_window_live)
continue;
if (largest_partition.elements < spec->partition_list.elements)
largest_partition = spec->partition_list;
if (largest_order_by.elements < spec->order_list.elements)
largest_order_by = spec->order_list;
}
}
if (can_compute_window_live && window_functions.elements && table_count == 1)
{
ha_rows examined_rows = 0;
ha_rows found_rows = 0;
ha_rows filesort_retval;
SORT_FIELD *s_order= (SORT_FIELD *) my_malloc(sizeof(SORT_FIELD) *
(largest_partition.elements + largest_order_by.elements) + 1,
MYF(MY_WME | MY_ZEROFILL | MY_THREAD_SPECIFIC));
size_t pos= 0;
for (ORDER* curr = largest_partition.first; curr; curr=curr->next, pos++)
s_order[pos].item = *curr->item;
for (ORDER* curr = largest_order_by.first; curr; curr=curr->next, pos++)
s_order[pos].item = *curr->item;
table[0]->sort.io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE),
MYF(MY_WME | MY_ZEROFILL|
MY_THREAD_SPECIFIC));
filesort_retval= filesort(thd, table[0], s_order,
(largest_partition.elements + largest_order_by.elements),
this->select, HA_POS_ERROR, FALSE,
&examined_rows, &found_rows,
this->explain->ops_tracker.report_sorting(thd));
table[0]->sort.found_records= filesort_retval;
join_tab->read_first_record = join_init_read_record;
join_tab->records= found_rows;
my_free(s_order);
}
else
#endif